@oneuptime/common 11.5.9 → 11.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/Index.ts +13 -0
- package/Models/AnalyticsModels/MetricItemAggMV1mByContainer.ts +178 -0
- package/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +179 -0
- package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +179 -0
- package/Models/AnalyticsModels/NetworkFlow.ts +397 -0
- package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +18 -0
- package/Models/DatabaseModels/GlobalConfig.ts +36 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/MarketingConversion.ts +198 -0
- package/Models/DatabaseModels/MetricSavedView.ts +34 -0
- package/Models/DatabaseModels/MetricType.ts +74 -0
- package/Models/DatabaseModels/NetworkDevice.ts +332 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +113 -0
- package/Models/DatabaseModels/NetworkInterface.ts +58 -0
- package/Models/DatabaseModels/Project.ts +37 -0
- package/Models/DatabaseModels/User.ts +37 -0
- package/Server/API/BaseAnalyticsAPI.ts +23 -0
- package/Server/API/EnterpriseLicenseAPI.ts +14 -0
- package/Server/EnvironmentConfig.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784191414522-AddCounterSemanticsToMetricType.ts +38 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784191414600-AddViewTypeToMetricSavedView.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784211212164-AddNetworkDeviceInventoryAndDiscoverySchedule.ts +108 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784218257664-AddEnterpriseLicenseNotificationColumns.ts +31 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784293516000-AddAttributionColumnsToUserAndProject.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784298000000-AddMarketingConversionTable.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.ts +101 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
- package/Server/Services/AnalyticsDatabaseService.ts +70 -1
- package/Server/Services/IncidentService.ts +13 -0
- package/Server/Services/Index.ts +10 -0
- package/Server/Services/MarketingConversionService.ts +10 -0
- package/Server/Services/MetricItemAggMV1mByContainerService.ts +31 -0
- package/Server/Services/MetricItemAggMV1mByK8sClusterService.ts +31 -0
- package/Server/Services/MetricItemAggMV1mByServiceService.ts +31 -0
- package/Server/Services/MetricService.ts +970 -122
- package/Server/Services/MonitorService.ts +14 -0
- package/Server/Services/NetworkDeviceOwnerTeamService.ts +42 -0
- package/Server/Services/NetworkDeviceOwnerUserService.ts +106 -0
- package/Server/Services/NetworkFlowService.ts +11 -0
- package/Server/Services/OnCallDutyPolicyService.ts +11 -1
- package/Server/Services/ProjectService.ts +136 -0
- package/Server/Services/SpanService.ts +13 -9
- package/Server/Services/StatusPageService.ts +10 -0
- package/Server/Services/TeamMemberService.ts +11 -0
- package/Server/Services/UserService.ts +59 -0
- package/Server/Types/AnalyticsDatabase/AggregateBy.ts +37 -7
- package/Server/Utils/Attribution.ts +99 -0
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +103 -0
- package/Server/Utils/Marketing/ConversionUploadProviders.ts +22 -0
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +270 -0
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +147 -0
- package/Server/Utils/Marketing/Providers/Meta.ts +148 -0
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +196 -0
- package/Server/Utils/Marketing/Providers/Reddit.ts +178 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +71 -7
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +31 -19
- package/Server/Utils/Monitor/MonitorIncident.ts +71 -7
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +57 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +94 -0
- package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +15 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +125 -14
- package/Server/Utils/ProductAnalytics.ts +133 -0
- package/Server/Utils/Telemetry/Telemetry.ts +39 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +21 -0
- package/Tests/Server/API/BaseAnalyticsAPI.test.ts +72 -0
- package/Tests/Server/Infrastructure/SemaphorePermit.test.ts +9 -1
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +21 -2
- package/Tests/Server/Services/MetricEntityMVKeyParity.test.ts +173 -0
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +1391 -0
- package/Tests/Server/Services/NetworkDeviceOwnerServices.test.ts +260 -0
- package/Tests/Server/Utils/Monitor/MonitorTemplateUtilNetworkDevice.test.ts +389 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +764 -0
- package/Tests/Types/BaseDatabase/AggregationIntervalUtil.test.ts +111 -2
- package/Tests/Types/Database/EndsWith.test.ts +64 -0
- package/Tests/Types/Database/GreaterThan.test.ts +62 -0
- package/Tests/Types/Database/GreaterThanOrEqual.test.ts +65 -0
- package/Tests/Types/Database/GreaterThanOrNull.test.ts +63 -0
- package/Tests/Types/Database/Includes.test.ts +65 -0
- package/Tests/Types/Database/IncludesAll.test.ts +65 -0
- package/Tests/Types/Database/IncludesNone.test.ts +65 -0
- package/Tests/Types/Database/IsNull.test.ts +44 -0
- package/Tests/Types/Database/LessThan.test.ts +62 -0
- package/Tests/Types/Database/LessThanOrEqual.test.ts +62 -0
- package/Tests/Types/Database/LessThanOrNull.test.ts +62 -0
- package/Tests/Types/Database/MultiSearch.test.ts +81 -0
- package/Tests/Types/Database/NotContains.test.ts +64 -0
- package/Tests/Types/Database/NotNull.test.ts +44 -0
- package/Tests/Types/Database/StartsWith.test.ts +64 -0
- package/Tests/Types/Monitor/MonitorStepSqlMonitor.test.ts +83 -1
- package/Tests/Types/Monitor/SnmpV3EnumParsing.test.ts +247 -0
- package/Tests/Types/Monitor/SnmpVendorTemplate.test.ts +263 -0
- package/Tests/UI/Components/XAxis.test.ts +5 -3
- package/Tests/Utils/EnterpriseLicenseUsage.test.ts +76 -0
- package/Tests/Utils/Metrics/MetricExplorerUrl.test.ts +622 -0
- package/Tests/Utils/Metrics/MetricFormulaEvaluator.test.ts +474 -0
- package/Tests/Utils/Telemetry/HeartbeatAvailability.test.ts +52 -4
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +11 -0
- package/Types/BaseDatabase/AggregateBy.ts +31 -2
- package/Types/BaseDatabase/AggregatedResult.ts +22 -0
- package/Types/BaseDatabase/AggregationInterval.ts +11 -0
- package/Types/BaseDatabase/AggregationIntervalUtil.ts +30 -5
- package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +16 -0
- package/Types/Email/EmailTemplateType.ts +3 -0
- package/Types/Marketing/MarketingConversion.ts +11 -0
- package/Types/Metrics/MetricQueryConfigData.ts +21 -0
- package/Types/Metrics/MetricQueryData.ts +8 -0
- package/Types/Metrics/MetricViewData.ts +8 -0
- package/Types/Monitor/MonitorMetricType.ts +8 -0
- package/Types/Monitor/MonitorStep.ts +9 -0
- package/Types/Monitor/MonitorStepSqlMonitor.ts +12 -0
- package/Types/Monitor/MonitorType.ts +2 -1
- package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +17 -12
- package/Types/Monitor/SnmpMonitor/CdpNeighbor.ts +13 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +37 -3
- package/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.ts +46 -0
- package/Types/Monitor/SnmpMonitor/SnmpEntityInfo.ts +15 -0
- package/Types/Monitor/SnmpMonitor/SnmpInterface.ts +8 -0
- package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +17 -8
- package/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.ts +53 -0
- package/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.ts +43 -0
- package/Types/Monitor/SnmpMonitor/SnmpSystemInfo.ts +16 -0
- package/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.ts +119 -3
- package/Types/NetFlow/NetworkFlowRecord.ts +25 -0
- package/Types/Syslog/SyslogMessage.ts +18 -0
- package/Types/Telemetry/TelemetrySavedViewState.ts +9 -0
- package/Types/Telemetry/TelemetrySavedViewType.ts +12 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +51 -0
- package/UI/Components/Charts/Bar/BarChart.tsx +51 -1
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +97 -26
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +328 -1
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +115 -0
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +328 -1
- package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +7 -0
- package/UI/Components/Charts/ChartLibrary/Types/FormattedReferenceRegion.ts +15 -0
- package/UI/Components/Charts/ChartLibrary/Types/FormattedTimeReferenceLine.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +51 -0
- package/UI/Components/Charts/Types/ReferenceRegionProps.ts +12 -0
- package/UI/Components/Charts/Types/TimeReferenceLineProps.ts +12 -0
- package/UI/Components/Charts/Types/XAxis/XAxisPrecision.ts +1 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +0 -0
- package/UI/Components/Charts/Utils/TimeAnnotation.ts +169 -0
- package/UI/Components/Charts/Utils/XAxis.ts +28 -4
- package/UI/Components/Icon/Icon.tsx +33 -13
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +9 -0
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +67 -0
- package/UI/Components/Page/ModelPage.tsx +6 -1
- package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +33 -4
- package/UI/Styles/Theme.css +10 -0
- package/UI/Utils/User.ts +41 -2
- package/Utils/Analytics.ts +8 -6
- package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +74 -0
- package/Utils/Metrics/MetricExplorerUrl.ts +771 -0
- package/Utils/Metrics/MetricFormulaEvaluator.ts +410 -46
- package/Utils/Monitor/NetworkTopologyUtil.ts +261 -49
- package/Utils/ValueFormatter.ts +22 -1
- package/build/dist/Models/AnalyticsModels/Index.js +13 -0
- package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByContainer.js +159 -0
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByContainer.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +158 -0
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +160 -0
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/NetworkFlow.js +351 -0
- package/build/dist/Models/AnalyticsModels/NetworkFlow.js.map +1 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +19 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +38 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +219 -0
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MetricSavedView.js +35 -0
- package/build/dist/Models/DatabaseModels/MetricSavedView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MetricType.js +76 -0
- package/build/dist/Models/DatabaseModels/MetricType.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +341 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +116 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +60 -0
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +38 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +38 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js +19 -0
- package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +8 -0
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +51 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784191414522-AddCounterSemanticsToMetricType.js +27 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784191414522-AddCounterSemanticsToMetricType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784191414600-AddViewTypeToMetricSavedView.js +21 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784191414600-AddViewTypeToMetricSavedView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784211212164-AddNetworkDeviceInventoryAndDiscoverySchedule.js +47 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784211212164-AddNetworkDeviceInventoryAndDiscoverySchedule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784218257664-AddEnterpriseLicenseNotificationColumns.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784218257664-AddEnterpriseLicenseNotificationColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784293516000-AddAttributionColumnsToUserAndProject.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784293516000-AddAttributionColumnsToUserAndProject.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784298000000-AddMarketingConversionTable.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784298000000-AddMarketingConversionTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +58 -4
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +12 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +10 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MarketingConversionService.js +9 -0
- package/build/dist/Server/Services/MarketingConversionService.js.map +1 -0
- package/build/dist/Server/Services/MetricItemAggMV1mByContainerService.js +29 -0
- package/build/dist/Server/Services/MetricItemAggMV1mByContainerService.js.map +1 -0
- package/build/dist/Server/Services/MetricItemAggMV1mByK8sClusterService.js +29 -0
- package/build/dist/Server/Services/MetricItemAggMV1mByK8sClusterService.js.map +1 -0
- package/build/dist/Server/Services/MetricItemAggMV1mByServiceService.js +29 -0
- package/build/dist/Server/Services/MetricItemAggMV1mByServiceService.js.map +1 -0
- package/build/dist/Server/Services/MetricService.js +761 -125
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +18 -5
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceOwnerTeamService.js +44 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceOwnerUserService.js +88 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/NetworkFlowService.js +9 -0
- package/build/dist/Server/Services/NetworkFlowService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyService.js +15 -6
- package/build/dist/Server/Services/OnCallDutyPolicyService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +107 -6
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/SpanService.js +8 -6
- package/build/dist/Server/Services/SpanService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +10 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +11 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +52 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js +33 -7
- package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js.map +1 -1
- package/build/dist/Server/Utils/Attribution.js +74 -0
- package/build/dist/Server/Utils/Attribution.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +40 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +20 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +177 -0
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +113 -0
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +109 -0
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +130 -0
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +126 -0
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +53 -9
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +21 -10
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +54 -11
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +43 -3
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +68 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +15 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +100 -15
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/ProductAnalytics.js +111 -0
- package/build/dist/Server/Utils/ProductAnalytics.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +32 -5
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +11 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
- package/build/dist/Types/BaseDatabase/AggregationInterval.js +11 -0
- package/build/dist/Types/BaseDatabase/AggregationInterval.js.map +1 -1
- package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js +33 -5
- package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +2 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingConversion.js +13 -0
- package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +6 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepSqlMonitor.js +3 -0
- package/build/dist/Types/Monitor/MonitorStepSqlMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +2 -1
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/CdpNeighbor.js +2 -0
- package/build/dist/Types/Monitor/SnmpMonitor/CdpNeighbor.js.map +1 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.js +41 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpEntityInfo.js +2 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpEntityInfo.js.map +1 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.js +48 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.js +38 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpSystemInfo.js +2 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpSystemInfo.js.map +1 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.js +96 -3
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.js.map +1 -1
- package/build/dist/Types/NetFlow/NetworkFlowRecord.js +2 -0
- package/build/dist/Types/NetFlow/NetworkFlowRecord.js.map +1 -0
- package/build/dist/Types/Syslog/SyslogMessage.js +2 -0
- package/build/dist/Types/Syslog/SyslogMessage.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetrySavedViewState.js +0 -7
- package/build/dist/Types/Telemetry/TelemetrySavedViewState.js.map +1 -1
- package/build/dist/Types/Telemetry/TelemetrySavedViewType.js +13 -0
- package/build/dist/Types/Telemetry/TelemetrySavedViewType.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +25 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +26 -2
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +36 -12
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +208 -6
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +52 -3
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +208 -6
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +6 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/FormattedReferenceRegion.js +2 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/FormattedReferenceRegion.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/FormattedTimeReferenceLine.js +2 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/FormattedTimeReferenceLine.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +25 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Types/ReferenceRegionProps.js +2 -0
- package/build/dist/UI/Components/Charts/Types/ReferenceRegionProps.js.map +1 -0
- package/build/dist/UI/Components/Charts/Types/TimeReferenceLineProps.js +2 -0
- package/build/dist/UI/Components/Charts/Types/TimeReferenceLineProps.js.map +1 -0
- package/build/dist/UI/Components/Charts/Types/XAxis/XAxisPrecision.js +1 -0
- package/build/dist/UI/Components/Charts/Types/XAxis/XAxisPrecision.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +0 -0
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js +121 -0
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +27 -4
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +15 -8
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +58 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/Page/ModelPage.js +7 -1
- package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +10 -4
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Utils/User.js +35 -2
- package/build/dist/UI/Utils/User.js.map +1 -1
- package/build/dist/Utils/Analytics.js +8 -5
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +57 -0
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
- package/build/dist/Utils/Metrics/MetricExplorerUrl.js +418 -0
- package/build/dist/Utils/Metrics/MetricExplorerUrl.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricFormulaEvaluator.js +296 -37
- package/build/dist/Utils/Metrics/MetricFormulaEvaluator.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +176 -40
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/ValueFormatter.js +20 -1
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { JSONObject, JSONValue } from "../../Types/JSON";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Sanitizers for the publicly-creatable attribution jsonb columns on User
|
|
5
|
+
* (clickIds, firstTouchAttribution). Unauthenticated signup can submit these,
|
|
6
|
+
* so — unlike the varchar(500)-bounded utm columns — they must be whitelisted
|
|
7
|
+
* and size-bounded here before they are persisted, copied onto projects, and
|
|
8
|
+
* forwarded to analytics.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const MAX_VALUE_LENGTH: number = 500;
|
|
12
|
+
|
|
13
|
+
const CLICK_ID_KEYS: Array<string> = [
|
|
14
|
+
"gclid",
|
|
15
|
+
"wbraid",
|
|
16
|
+
"gbraid",
|
|
17
|
+
"fbclid",
|
|
18
|
+
"msclkid",
|
|
19
|
+
"li_fat_id",
|
|
20
|
+
"twclid",
|
|
21
|
+
"rdt_cid",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const FIRST_TOUCH_STRING_KEYS: Array<string> = [
|
|
25
|
+
"utmSource",
|
|
26
|
+
"utmMedium",
|
|
27
|
+
"utmCampaign",
|
|
28
|
+
"utmTerm",
|
|
29
|
+
"utmContent",
|
|
30
|
+
"landingUrl",
|
|
31
|
+
"referrer",
|
|
32
|
+
"timestamp",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
type SanitizeStringValueFunction = (value: JSONValue) => string | null;
|
|
36
|
+
|
|
37
|
+
const sanitizeStringValue: SanitizeStringValueFunction = (
|
|
38
|
+
value: JSONValue,
|
|
39
|
+
): string | null => {
|
|
40
|
+
if (typeof value === "string" && value.length > 0) {
|
|
41
|
+
return value.slice(0, MAX_VALUE_LENGTH);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof value === "number") {
|
|
45
|
+
return value.toString().slice(0, MAX_VALUE_LENGTH);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default class Attribution {
|
|
52
|
+
// Whitelist known ad-platform click id keys; bound each value to 500 chars.
|
|
53
|
+
public static sanitizeClickIds(value: JSONValue): JSONObject | undefined {
|
|
54
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const input: JSONObject = value as JSONObject;
|
|
59
|
+
const result: JSONObject = {};
|
|
60
|
+
|
|
61
|
+
for (const key of CLICK_ID_KEYS) {
|
|
62
|
+
const sanitized: string | null = sanitizeStringValue(input[key]);
|
|
63
|
+
if (sanitized) {
|
|
64
|
+
result[key] = sanitized;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Whitelist the first-touch shape written by the marketing site.
|
|
72
|
+
public static sanitizeFirstTouchAttribution(
|
|
73
|
+
value: JSONValue,
|
|
74
|
+
): JSONObject | undefined {
|
|
75
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const input: JSONObject = value as JSONObject;
|
|
80
|
+
const result: JSONObject = {};
|
|
81
|
+
|
|
82
|
+
for (const key of FIRST_TOUCH_STRING_KEYS) {
|
|
83
|
+
const sanitized: string | null = sanitizeStringValue(input[key]);
|
|
84
|
+
if (sanitized) {
|
|
85
|
+
result[key] = sanitized;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const clickIds: JSONObject | undefined = this.sanitizeClickIds(
|
|
90
|
+
input["clickIds"],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (clickIds) {
|
|
94
|
+
result["clickIds"] = clickIds;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { AxiosError } from "axios";
|
|
2
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
3
|
+
import { MarketingConversionType } from "../../../Types/Marketing/MarketingConversion";
|
|
4
|
+
import MarketingConversion from "../../../Models/DatabaseModels/MarketingConversion";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* One implementation per ad platform. The MarketingConversions worker job
|
|
8
|
+
* iterates all configured providers and uploads every pending conversion to
|
|
9
|
+
* each of them, tracking per-provider status in
|
|
10
|
+
* MarketingConversion.uploadState (keyed by `key`).
|
|
11
|
+
*
|
|
12
|
+
* Contract:
|
|
13
|
+
* - getSkipReason returns a human-readable reason when this conversion can
|
|
14
|
+
* never be uploaded to this platform (wrong/missing click id, outside the
|
|
15
|
+
* platform's upload window, provider not configured for this conversion
|
|
16
|
+
* type). Null means uploadable.
|
|
17
|
+
* - upload() sends one batch. Per-conversion PERMANENT rejections are
|
|
18
|
+
* returned in the result keyed by array index; they will not be retried.
|
|
19
|
+
* Transport/auth-level failures must THROW — the whole batch stays pending
|
|
20
|
+
* and is retried on the next run (bounded by the job's attempt cap), so
|
|
21
|
+
* providers should be safe against duplicate delivery where the platform
|
|
22
|
+
* supports an idempotency/dedup key.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export interface ConversionUploadBatchResult {
|
|
26
|
+
// Index into the submitted batch -> permanent failure message.
|
|
27
|
+
permanentFailures: Map<number, string>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ConversionSkip {
|
|
31
|
+
reason: string;
|
|
32
|
+
/*
|
|
33
|
+
* Permanent skips (missing click id, outside the platform's upload window)
|
|
34
|
+
* are recorded as Skipped and never revisited. Non-permanent skips
|
|
35
|
+
* (missing provider configuration such as a conversion action id) leave
|
|
36
|
+
* the conversion pending so it uploads once the configuration is added.
|
|
37
|
+
*/
|
|
38
|
+
isPermanent: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default abstract class ConversionUploadProvider {
|
|
42
|
+
// Stable key used in MarketingConversion.uploadState. Never change it.
|
|
43
|
+
public abstract readonly key: string;
|
|
44
|
+
public abstract readonly displayName: string;
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* Max conversions this provider accepts in one upload() call. Providers
|
|
48
|
+
* that issue one HTTP request per conversion must keep this small so a
|
|
49
|
+
* single run finishes well inside the worker job timeout.
|
|
50
|
+
*/
|
|
51
|
+
public readonly maxBatchSize: number = 500;
|
|
52
|
+
|
|
53
|
+
public abstract isConfigured(): boolean;
|
|
54
|
+
|
|
55
|
+
public abstract getSkipReason(
|
|
56
|
+
conversion: MarketingConversion,
|
|
57
|
+
): ConversionSkip | null;
|
|
58
|
+
|
|
59
|
+
public abstract upload(
|
|
60
|
+
conversions: Array<MarketingConversion>,
|
|
61
|
+
): Promise<ConversionUploadBatchResult>;
|
|
62
|
+
|
|
63
|
+
protected getClickId(
|
|
64
|
+
conversion: MarketingConversion,
|
|
65
|
+
clickIdKey: string,
|
|
66
|
+
): string | undefined {
|
|
67
|
+
const clickIds: JSONObject = conversion.clickIds || {};
|
|
68
|
+
const value: unknown = clickIds[clickIdKey];
|
|
69
|
+
return typeof value === "string" && value ? value : undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected getValueInUSD(conversion: MarketingConversion): number | undefined {
|
|
73
|
+
if (
|
|
74
|
+
conversion.conversionValueInUSDCents === undefined ||
|
|
75
|
+
conversion.conversionValueInUSDCents === null ||
|
|
76
|
+
conversion.conversionValueInUSDCents <= 0
|
|
77
|
+
) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return conversion.conversionValueInUSDCents / 100;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
protected isSignUp(conversion: MarketingConversion): boolean {
|
|
84
|
+
return conversion.conversionType === MarketingConversionType.SignUp;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected isOlderThanDays(
|
|
88
|
+
conversion: MarketingConversion,
|
|
89
|
+
days: number,
|
|
90
|
+
): boolean {
|
|
91
|
+
const conversionAt: Date = conversion.conversionAt || new Date();
|
|
92
|
+
return Date.now() - conversionAt.getTime() > days * 24 * 60 * 60 * 1000;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public static getErrorMessage(err: unknown): string {
|
|
96
|
+
if (err instanceof AxiosError) {
|
|
97
|
+
return `HTTP ${err.response?.status || "?"}: ${JSON.stringify(
|
|
98
|
+
err.response?.data || err.message,
|
|
99
|
+
).slice(0, 900)}`;
|
|
100
|
+
}
|
|
101
|
+
return (err as Error)?.message || "Unknown error";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import ConversionUploadProvider from "./ConversionUploadProvider";
|
|
2
|
+
import GoogleAdsProvider from "./Providers/GoogleAds";
|
|
3
|
+
import LinkedInProvider from "./Providers/LinkedIn";
|
|
4
|
+
import MetaProvider from "./Providers/Meta";
|
|
5
|
+
import MicrosoftAdsProvider from "./Providers/MicrosoftAds";
|
|
6
|
+
import RedditProvider from "./Providers/Reddit";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* All ad platforms OneUptime can upload offline conversions to. To add a
|
|
10
|
+
* platform: implement ConversionUploadProvider (one file under Providers/),
|
|
11
|
+
* add its env config to EnvironmentConfig, and list it here — no schema or
|
|
12
|
+
* worker changes needed. Unconfigured providers are ignored at runtime.
|
|
13
|
+
*/
|
|
14
|
+
const AllConversionUploadProviders: Array<ConversionUploadProvider> = [
|
|
15
|
+
new GoogleAdsProvider(),
|
|
16
|
+
new MetaProvider(),
|
|
17
|
+
new MicrosoftAdsProvider(),
|
|
18
|
+
new LinkedInProvider(),
|
|
19
|
+
new RedditProvider(),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export default AllConversionUploadProviders;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import axios, { AxiosResponse } from "axios";
|
|
2
|
+
import {
|
|
3
|
+
GoogleAdsApiVersion,
|
|
4
|
+
GoogleAdsCustomerId,
|
|
5
|
+
GoogleAdsDeveloperToken,
|
|
6
|
+
GoogleAdsLoginCustomerId,
|
|
7
|
+
GoogleAdsOAuthClientId,
|
|
8
|
+
GoogleAdsOAuthClientSecret,
|
|
9
|
+
GoogleAdsOAuthRefreshToken,
|
|
10
|
+
GoogleAdsPaidSubscriptionConversionActionId,
|
|
11
|
+
GoogleAdsSignUpConversionActionId,
|
|
12
|
+
} from "../../../EnvironmentConfig";
|
|
13
|
+
import ConversionUploadProvider, {
|
|
14
|
+
ConversionSkip,
|
|
15
|
+
ConversionUploadBatchResult,
|
|
16
|
+
} from "../ConversionUploadProvider";
|
|
17
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
18
|
+
import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
|
|
19
|
+
import logger from "../../Logger";
|
|
20
|
+
|
|
21
|
+
const REQUEST_TIMEOUT_MS: number = 30000;
|
|
22
|
+
|
|
23
|
+
// Google Ads accepts click conversions up to 90 days after the click.
|
|
24
|
+
const GOOGLE_MAX_CONVERSION_AGE_IN_DAYS: number = 90;
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Google Ads offline click conversions
|
|
28
|
+
* (customers/{id}:uploadClickConversions) with OAuth2 refresh-token auth.
|
|
29
|
+
* Supports gclid, wbraid and gbraid click ids. Uses partialFailure so valid
|
|
30
|
+
* conversions succeed even when others in the batch fail.
|
|
31
|
+
*/
|
|
32
|
+
export default class GoogleAdsProvider extends ConversionUploadProvider {
|
|
33
|
+
public override readonly key: string = "google";
|
|
34
|
+
public override readonly displayName: string = "Google Ads";
|
|
35
|
+
|
|
36
|
+
private cachedAccessToken: string | null = null;
|
|
37
|
+
private cachedAccessTokenExpiresAt: number = 0;
|
|
38
|
+
|
|
39
|
+
public override isConfigured(): boolean {
|
|
40
|
+
return Boolean(
|
|
41
|
+
GoogleAdsDeveloperToken &&
|
|
42
|
+
GoogleAdsOAuthClientId &&
|
|
43
|
+
GoogleAdsOAuthClientSecret &&
|
|
44
|
+
GoogleAdsOAuthRefreshToken &&
|
|
45
|
+
GoogleAdsCustomerId,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public override getSkipReason(
|
|
50
|
+
conversion: MarketingConversion,
|
|
51
|
+
): ConversionSkip | null {
|
|
52
|
+
if (!this.getGoogleClickId(conversion)) {
|
|
53
|
+
return {
|
|
54
|
+
reason: "No Google click id (gclid/wbraid/gbraid)",
|
|
55
|
+
isPermanent: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!this.getConversionActionId(conversion)) {
|
|
60
|
+
// Config gap — leave pending so it uploads once the id is configured.
|
|
61
|
+
return {
|
|
62
|
+
reason:
|
|
63
|
+
"No Google Ads conversion action id configured for this conversion type",
|
|
64
|
+
isPermanent: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this.isOlderThanDays(conversion, GOOGLE_MAX_CONVERSION_AGE_IN_DAYS)) {
|
|
69
|
+
return {
|
|
70
|
+
reason: "Conversion older than Google's 90-day click window",
|
|
71
|
+
isPermanent: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private getGoogleClickId(conversion: MarketingConversion): JSONObject | null {
|
|
79
|
+
const gclid: string | undefined = this.getClickId(conversion, "gclid");
|
|
80
|
+
if (gclid) {
|
|
81
|
+
return { gclid: gclid };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const wbraid: string | undefined = this.getClickId(conversion, "wbraid");
|
|
85
|
+
if (wbraid) {
|
|
86
|
+
return { wbraid: wbraid };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const gbraid: string | undefined = this.getClickId(conversion, "gbraid");
|
|
90
|
+
if (gbraid) {
|
|
91
|
+
return { gbraid: gbraid };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private getConversionActionId(conversion: MarketingConversion): string {
|
|
98
|
+
return this.isSignUp(conversion)
|
|
99
|
+
? GoogleAdsSignUpConversionActionId
|
|
100
|
+
: GoogleAdsPaidSubscriptionConversionActionId;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private async getAccessToken(): Promise<string> {
|
|
104
|
+
if (
|
|
105
|
+
this.cachedAccessToken &&
|
|
106
|
+
Date.now() < this.cachedAccessTokenExpiresAt
|
|
107
|
+
) {
|
|
108
|
+
return this.cachedAccessToken;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const params: URLSearchParams = new URLSearchParams();
|
|
112
|
+
params.append("client_id", GoogleAdsOAuthClientId);
|
|
113
|
+
params.append("client_secret", GoogleAdsOAuthClientSecret);
|
|
114
|
+
params.append("refresh_token", GoogleAdsOAuthRefreshToken);
|
|
115
|
+
params.append("grant_type", "refresh_token");
|
|
116
|
+
|
|
117
|
+
const response: AxiosResponse<{
|
|
118
|
+
access_token?: string;
|
|
119
|
+
expires_in?: number;
|
|
120
|
+
}> = await axios.post(
|
|
121
|
+
"https://oauth2.googleapis.com/token",
|
|
122
|
+
params.toString(),
|
|
123
|
+
{
|
|
124
|
+
headers: {
|
|
125
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
126
|
+
},
|
|
127
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const accessToken: string | undefined = response.data?.access_token;
|
|
132
|
+
|
|
133
|
+
if (!accessToken) {
|
|
134
|
+
throw new Error("Google OAuth token response had no access_token");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.cachedAccessToken = accessToken;
|
|
138
|
+
// Refresh one minute before actual expiry.
|
|
139
|
+
this.cachedAccessTokenExpiresAt =
|
|
140
|
+
Date.now() + ((response.data?.expires_in || 3600) - 60) * 1000;
|
|
141
|
+
|
|
142
|
+
return accessToken;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Format Google Ads expects: "yyyy-mm-dd hh:mm:ss+00:00".
|
|
146
|
+
private formatConversionDateTime(date: Date): string {
|
|
147
|
+
const iso: string = date.toISOString(); // 2026-07-18T12:34:56.789Z
|
|
148
|
+
return `${iso.slice(0, 10)} ${iso.slice(11, 19)}+00:00`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public override async upload(
|
|
152
|
+
conversions: Array<MarketingConversion>,
|
|
153
|
+
): Promise<ConversionUploadBatchResult> {
|
|
154
|
+
const accessToken: string = await this.getAccessToken();
|
|
155
|
+
|
|
156
|
+
const headers: JSONObject = {
|
|
157
|
+
Authorization: `Bearer ${accessToken}`,
|
|
158
|
+
"developer-token": GoogleAdsDeveloperToken,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (GoogleAdsLoginCustomerId) {
|
|
162
|
+
headers["login-customer-id"] = GoogleAdsLoginCustomerId;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const body: JSONObject = {
|
|
166
|
+
conversions: conversions.map((conversion: MarketingConversion) => {
|
|
167
|
+
const payload: JSONObject = {
|
|
168
|
+
...(this.getGoogleClickId(conversion) || {}),
|
|
169
|
+
conversionAction: `customers/${GoogleAdsCustomerId}/conversionActions/${this.getConversionActionId(
|
|
170
|
+
conversion,
|
|
171
|
+
)}`,
|
|
172
|
+
conversionDateTime: this.formatConversionDateTime(
|
|
173
|
+
conversion.conversionAt || new Date(),
|
|
174
|
+
),
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const valueInUSD: number | undefined = this.getValueInUSD(conversion);
|
|
178
|
+
|
|
179
|
+
if (valueInUSD !== undefined) {
|
|
180
|
+
payload["conversionValue"] = valueInUSD;
|
|
181
|
+
payload["currencyCode"] = "USD";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return payload;
|
|
185
|
+
}),
|
|
186
|
+
partialFailure: true,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const url: string = `https://googleads.googleapis.com/${GoogleAdsApiVersion}/customers/${GoogleAdsCustomerId}:uploadClickConversions`;
|
|
190
|
+
|
|
191
|
+
const response: AxiosResponse<JSONObject> = await axios.post(url, body, {
|
|
192
|
+
headers: headers as Record<string, string>,
|
|
193
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return this.parsePartialFailure(response.data);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/*
|
|
200
|
+
* partialFailureError.details[].errors[] entries carry the index of the
|
|
201
|
+
* failing operation in location.fieldPathElements ({fieldName:
|
|
202
|
+
* "conversions", index: N}).
|
|
203
|
+
*/
|
|
204
|
+
private parsePartialFailure(
|
|
205
|
+
responseData: JSONObject,
|
|
206
|
+
): ConversionUploadBatchResult {
|
|
207
|
+
const permanentFailures: Map<number, string> = new Map<number, string>();
|
|
208
|
+
|
|
209
|
+
const partialFailureError: JSONObject | undefined = responseData?.[
|
|
210
|
+
"partialFailureError"
|
|
211
|
+
] as JSONObject | undefined;
|
|
212
|
+
|
|
213
|
+
if (!partialFailureError) {
|
|
214
|
+
return { permanentFailures };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const details: Array<JSONObject> =
|
|
218
|
+
(partialFailureError["details"] as Array<JSONObject>) || [];
|
|
219
|
+
|
|
220
|
+
let alreadyExistsCount: number = 0;
|
|
221
|
+
|
|
222
|
+
for (const detail of details) {
|
|
223
|
+
const errors: Array<JSONObject> =
|
|
224
|
+
(detail["errors"] as Array<JSONObject>) || [];
|
|
225
|
+
|
|
226
|
+
for (const error of errors) {
|
|
227
|
+
const message: string =
|
|
228
|
+
(error["message"] as string) || "Unknown Google Ads error";
|
|
229
|
+
|
|
230
|
+
/*
|
|
231
|
+
* A duplicate rejection means the conversion WAS counted by a prior
|
|
232
|
+
* run (e.g. crash between upload and status write) — that is a
|
|
233
|
+
* success, not a failure.
|
|
234
|
+
*/
|
|
235
|
+
if (JSON.stringify(error).includes("ALREADY_EXISTS")) {
|
|
236
|
+
alreadyExistsCount++;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const fieldPathElements: Array<JSONObject> =
|
|
241
|
+
((error["location"] as JSONObject)?.[
|
|
242
|
+
"fieldPathElements"
|
|
243
|
+
] as Array<JSONObject>) || [];
|
|
244
|
+
|
|
245
|
+
for (const element of fieldPathElements) {
|
|
246
|
+
if (
|
|
247
|
+
element["fieldName"] === "conversions" &&
|
|
248
|
+
typeof element["index"] === "number"
|
|
249
|
+
) {
|
|
250
|
+
permanentFailures.set(element["index"] as number, message);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
* A partial failure with no parseable indexes should not be silently
|
|
258
|
+
* treated as full success.
|
|
259
|
+
*/
|
|
260
|
+
if (permanentFailures.size === 0 && alreadyExistsCount === 0) {
|
|
261
|
+
logger.error(
|
|
262
|
+
`Google Ads partial failure with unparseable indexes: ${JSON.stringify(
|
|
263
|
+
partialFailureError,
|
|
264
|
+
).slice(0, 1000)}`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { permanentFailures };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import axios, { AxiosError } from "axios";
|
|
2
|
+
import {
|
|
3
|
+
LinkedInApiVersion,
|
|
4
|
+
LinkedInConversionsAccessToken,
|
|
5
|
+
LinkedInPaidSubscriptionConversionId,
|
|
6
|
+
LinkedInSignUpConversionId,
|
|
7
|
+
} from "../../../EnvironmentConfig";
|
|
8
|
+
import ConversionUploadProvider, {
|
|
9
|
+
ConversionSkip,
|
|
10
|
+
ConversionUploadBatchResult,
|
|
11
|
+
} from "../ConversionUploadProvider";
|
|
12
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
13
|
+
import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
|
|
14
|
+
|
|
15
|
+
const REQUEST_TIMEOUT_MS: number = 30000;
|
|
16
|
+
|
|
17
|
+
// LinkedIn accepts conversions up to 90 days after the interaction.
|
|
18
|
+
const LINKEDIN_MAX_CONVERSION_AGE_IN_DAYS: number = 90;
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* LinkedIn Conversions API (api.linkedin.com/rest/conversionEvents) using
|
|
22
|
+
* the li_fat_id first-party click id. Conversion rules are referenced by
|
|
23
|
+
* urn:lla:llaPartnerConversion:{id}. Events are posted one at a time (small
|
|
24
|
+
* volumes); auth-level failures abort the batch for retry, other per-event
|
|
25
|
+
* rejections are recorded as permanent.
|
|
26
|
+
*
|
|
27
|
+
* Note: LinkedIn access tokens expire (typically 60 days) — the token in
|
|
28
|
+
* LINKEDIN_CONVERSIONS_ACCESS_TOKEN must be rotated externally.
|
|
29
|
+
*/
|
|
30
|
+
export default class LinkedInProvider extends ConversionUploadProvider {
|
|
31
|
+
public override readonly key: string = "linkedin";
|
|
32
|
+
public override readonly displayName: string = "LinkedIn";
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* LinkedIn has no batch endpoint, so upload() issues one request per
|
|
36
|
+
* conversion — keep runs short enough to finish inside the job timeout.
|
|
37
|
+
*/
|
|
38
|
+
public override readonly maxBatchSize: number = 50;
|
|
39
|
+
|
|
40
|
+
public override isConfigured(): boolean {
|
|
41
|
+
return Boolean(LinkedInConversionsAccessToken);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public override getSkipReason(
|
|
45
|
+
conversion: MarketingConversion,
|
|
46
|
+
): ConversionSkip | null {
|
|
47
|
+
if (!this.getClickId(conversion, "li_fat_id")) {
|
|
48
|
+
return {
|
|
49
|
+
reason: "No LinkedIn click id (li_fat_id)",
|
|
50
|
+
isPermanent: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!this.getConversionRuleId(conversion)) {
|
|
55
|
+
// Config gap — leave pending so it uploads once the id is configured.
|
|
56
|
+
return {
|
|
57
|
+
reason:
|
|
58
|
+
"No LinkedIn conversion rule id configured for this conversion type",
|
|
59
|
+
isPermanent: false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.isOlderThanDays(conversion, LINKEDIN_MAX_CONVERSION_AGE_IN_DAYS)) {
|
|
64
|
+
return {
|
|
65
|
+
reason: "Conversion older than LinkedIn's 90-day window",
|
|
66
|
+
isPermanent: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private getConversionRuleId(conversion: MarketingConversion): string {
|
|
74
|
+
return this.isSignUp(conversion)
|
|
75
|
+
? LinkedInSignUpConversionId
|
|
76
|
+
: LinkedInPaidSubscriptionConversionId;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public override async upload(
|
|
80
|
+
conversions: Array<MarketingConversion>,
|
|
81
|
+
): Promise<ConversionUploadBatchResult> {
|
|
82
|
+
const permanentFailures: Map<number, string> = new Map<number, string>();
|
|
83
|
+
|
|
84
|
+
for (let i: number = 0; i < conversions.length; i++) {
|
|
85
|
+
const conversion: MarketingConversion = conversions[i]!;
|
|
86
|
+
|
|
87
|
+
const body: JSONObject = {
|
|
88
|
+
conversion: `urn:lla:llaPartnerConversion:${this.getConversionRuleId(
|
|
89
|
+
conversion,
|
|
90
|
+
)}`,
|
|
91
|
+
conversionHappenedAt: (conversion.conversionAt || new Date()).getTime(),
|
|
92
|
+
// Dedup key: a retried batch must not double-count conversions.
|
|
93
|
+
eventId: conversion.id!.toString(),
|
|
94
|
+
user: {
|
|
95
|
+
userIds: [
|
|
96
|
+
{
|
|
97
|
+
idType: "LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID",
|
|
98
|
+
idValue: this.getClickId(conversion, "li_fat_id") || "",
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const valueInUSD: number | undefined = this.getValueInUSD(conversion);
|
|
105
|
+
|
|
106
|
+
if (valueInUSD !== undefined) {
|
|
107
|
+
body["conversionValue"] = {
|
|
108
|
+
currencyCode: "USD",
|
|
109
|
+
amount: valueInUSD.toFixed(2),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
await axios.post(
|
|
115
|
+
"https://api.linkedin.com/rest/conversionEvents",
|
|
116
|
+
body,
|
|
117
|
+
{
|
|
118
|
+
headers: {
|
|
119
|
+
Authorization: `Bearer ${LinkedInConversionsAccessToken}`,
|
|
120
|
+
"LinkedIn-Version": LinkedInApiVersion,
|
|
121
|
+
"X-Restli-Protocol-Version": "2.0.0",
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
},
|
|
124
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
const status: number | undefined = (err as AxiosError).response?.status;
|
|
129
|
+
|
|
130
|
+
/*
|
|
131
|
+
* Only definitive per-event validation rejections are permanent.
|
|
132
|
+
* Everything else — auth/token problems, rate limits, 5xx, and
|
|
133
|
+
* network errors (no response, so no status) — is transport-level:
|
|
134
|
+
* throw so the whole batch stays pending and is retried. eventId
|
|
135
|
+
* makes redelivery idempotent on LinkedIn's side.
|
|
136
|
+
*/
|
|
137
|
+
if (status !== 400 && status !== 422) {
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
permanentFailures.set(i, ConversionUploadProvider.getErrorMessage(err));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { permanentFailures };
|
|
146
|
+
}
|
|
147
|
+
}
|