|
| 1 | +from django.conf import settings |
| 2 | +from django.contrib import messages |
| 3 | +from django.http import HttpRequest, HttpResponse |
| 4 | +from django.utils.safestring import mark_safe |
| 5 | +from django.utils.translation import gettext_lazy as _ |
| 6 | + |
| 7 | + |
| 8 | +class ProductAnnouncementManager: |
| 9 | + |
| 10 | + """Base class for centralized helper methods""" |
| 11 | + |
| 12 | + base_try_free = "Try today for free" |
| 13 | + base_contact_us = "email us at" |
| 14 | + base_email_address = "[email protected]" |
| 15 | + ui_try_free = f'<b><a href="https://cloud.defectdojo.com/accounts/onboarding/plg_step_1" target="_blank">{base_try_free}</a></b>' |
| 16 | + ui_contact_us = f'{base_contact_us} <b><a href="mailto:{base_email_address}">{base_email_address}</a></b>' |
| 17 | + ui_outreach = f"{ui_try_free} or {ui_contact_us}." |
| 18 | + api_outreach = f"{base_try_free} or {base_contact_us} {base_email_address}" |
| 19 | + |
| 20 | + def __init__( |
| 21 | + self, |
| 22 | + *args: list, |
| 23 | + request: HttpRequest = None, |
| 24 | + response: HttpResponse = None, |
| 25 | + response_data: dict | None = None, |
| 26 | + **kwargs: dict, |
| 27 | + ): |
| 28 | + """Skip all this if the CREATE_CLOUD_BANNER is not set""" |
| 29 | + if not settings.CREATE_CLOUD_BANNER: |
| 30 | + return |
| 31 | + # Fill in the vars if the were supplied correctly |
| 32 | + if request is not None and isinstance(request, HttpRequest): |
| 33 | + self._add_django_message( |
| 34 | + request=request, |
| 35 | + message=mark_safe(f"{self.base_message} {self.ui_outreach}"), |
| 36 | + ) |
| 37 | + elif response is not None and isinstance(response, HttpResponse): |
| 38 | + response.data = self._add_api_response_key( |
| 39 | + message=f"{self.base_message} {self.api_outreach}", data=response.data, |
| 40 | + ) |
| 41 | + elif response_data is not None and isinstance(response_data, dict): |
| 42 | + response_data = self._add_api_response_key( |
| 43 | + message=f"{self.base_message} {self.api_outreach}", data=response_data, |
| 44 | + ) |
| 45 | + else: |
| 46 | + msg = "At least one of request, response, or response_data must be supplied" |
| 47 | + raise ValueError(msg) |
| 48 | + |
| 49 | + def _add_django_message(self, request: HttpRequest, message: str): |
| 50 | + """Add a message to the UI""" |
| 51 | + messages.add_message( |
| 52 | + request=request, |
| 53 | + level=messages.INFO, |
| 54 | + message=_(message), |
| 55 | + extra_tags="alert-info", |
| 56 | + ) |
| 57 | + |
| 58 | + def _add_api_response_key(self, message: str, data: dict) -> dict: |
| 59 | + """Update the response data in place""" |
| 60 | + if (feature_list := data.get("pro")) is not None and isinstance( |
| 61 | + feature_list, |
| 62 | + list, |
| 63 | + ): |
| 64 | + data["pro"] = [*feature_list, _(message)] |
| 65 | + else: |
| 66 | + data["pro"] = [_(message)] |
| 67 | + return data |
| 68 | + |
| 69 | + |
| 70 | +class ErrorPageProductAnnouncement(ProductAnnouncementManager): |
| 71 | + def __init__( |
| 72 | + self, |
| 73 | + *args: list, |
| 74 | + request: HttpRequest = None, |
| 75 | + response: HttpResponse = None, |
| 76 | + response_data: dict | None = None, |
| 77 | + **kwargs: dict, |
| 78 | + ): |
| 79 | + self.base_message = "Pro comes with support." |
| 80 | + super().__init__( |
| 81 | + *args, |
| 82 | + request=request, |
| 83 | + response=response, |
| 84 | + response_data=response_data, |
| 85 | + **kwargs, |
| 86 | + ) |
| 87 | + |
| 88 | + |
| 89 | +class LargeScanSizeProductAnnouncement(ProductAnnouncementManager): |
| 90 | + def __init__( |
| 91 | + self, |
| 92 | + *args: list, |
| 93 | + request: HttpRequest = None, |
| 94 | + response: HttpResponse = None, |
| 95 | + response_data: dict | None = None, |
| 96 | + duration: float = 0.0, # seconds |
| 97 | + **kwargs: dict, |
| 98 | + ): |
| 99 | + self.trigger_threshold = 60.0 |
| 100 | + minute_duration = round(duration / 60.0) |
| 101 | + self.base_message = f"Your import took about {minute_duration} minute(s). Did you know Pro has async imports?" |
| 102 | + if duration > self.trigger_threshold: |
| 103 | + super().__init__( |
| 104 | + *args, |
| 105 | + request=request, |
| 106 | + response=response, |
| 107 | + response_data=response_data, |
| 108 | + **kwargs, |
| 109 | + ) |
| 110 | + |
| 111 | + |
| 112 | +class LongRunningRequestProductAnnouncement(ProductAnnouncementManager): |
| 113 | + def __init__( |
| 114 | + self, |
| 115 | + *args: list, |
| 116 | + request: HttpRequest = None, |
| 117 | + response: HttpResponse = None, |
| 118 | + response_data: dict | None = None, |
| 119 | + duration: float = 0.0, # seconds |
| 120 | + **kwargs: dict, |
| 121 | + ): |
| 122 | + self.trigger_threshold = 15.0 |
| 123 | + self.base_message = "Did you know, Pro has a new UI and is performance tested up to 22M findings?" |
| 124 | + if duration > self.trigger_threshold: |
| 125 | + super().__init__( |
| 126 | + *args, |
| 127 | + request=request, |
| 128 | + response=response, |
| 129 | + response_data=response_data, |
| 130 | + **kwargs, |
| 131 | + ) |
| 132 | + |
| 133 | + |
| 134 | +class ScanTypeProductAnnouncement(ProductAnnouncementManager): |
| 135 | + supported_scan_types = [ |
| 136 | + "Snyk Scan", |
| 137 | + "Semgrep JSON Report", |
| 138 | + "Burp Enterprise Scan", |
| 139 | + "AWS Security Hub Scan", |
| 140 | + "Probely Scan", # No OS support here |
| 141 | + "Checkmarx One Scan", |
| 142 | + "Tenable Scan", |
| 143 | + "SonarQube Scan", |
| 144 | + "Dependency Track Finding Packaging Format (FPF) Export", |
| 145 | + "Wiz Scan", |
| 146 | + ] |
| 147 | + |
| 148 | + def __init__( |
| 149 | + self, |
| 150 | + *args: list, |
| 151 | + request: HttpRequest = None, |
| 152 | + response: HttpResponse = None, |
| 153 | + response_data: dict | None = None, |
| 154 | + scan_type: str | None = None, |
| 155 | + **kwargs: dict, |
| 156 | + ): |
| 157 | + self.base_message = ( |
| 158 | + f"Did you know, Pro has an automated no-code connector for {scan_type}?" |
| 159 | + ) |
| 160 | + if scan_type in self.supported_scan_types: |
| 161 | + super().__init__( |
| 162 | + *args, |
| 163 | + request=request, |
| 164 | + response=response, |
| 165 | + response_data=response_data, |
| 166 | + **kwargs, |
| 167 | + ) |
0 commit comments