Browse Source

chore(plugins): Simplify plugin deprecation (#30336)

There were a few TODOs in the code that were nondescript and not useful, so this PR addresses them.

In this PR, the deprecation and shadow deprecation workflow for plugins is reworked to be more streamlined. Now, deprecation_date and SHADOW_DEPRECATED_PLUGINS are both methods to perform deprecation.

If we want an immediate deprecation, adding the slug/feature to SHADOW_DEPRECATED_PLUGINS will hide and 404 the plugin's usage/installation. This method allows certain orgs to be flagged into the plugin's operation.

If we want to blanket disabled the plugin for everyone, we can do so with deprecation_date (Added in #28137). We just specify the date, and after that date the plugin will no longer appear in the UI. During the time between that date and now, an alert will still appear on the integration to warn users before installing.
Leander Rodrigues 3 years ago
parent
commit
621a0f2141

+ 3 - 9
src/sentry/api/endpoints/organization_plugins_configs.py

@@ -3,7 +3,7 @@ from rest_framework.response import Response
 
 from sentry.api.bases.organization import OrganizationEndpoint
 from sentry.api.serializers import serialize
-from sentry.api.serializers.models.plugin import SHADOW_DEPRECATED_PLUGINS, PluginSerializer
+from sentry.api.serializers.models.plugin import PluginSerializer
 from sentry.constants import ObjectStatus
 from sentry.models import Project, ProjectOption
 from sentry.plugins.base import plugins
@@ -34,11 +34,6 @@ class OrganizationPluginsConfigsEndpoint(OrganizationEndpoint):
         if not desired_plugins:
             desired_plugins = list(plugins.plugin_that_can_be_configured())
 
-        # True if only one plugin is desired, and it has been shadow deprecated
-        is_shadow_deprecated_detailed_view = (
-            len(desired_plugins) == 1 and desired_plugins[0].slug in SHADOW_DEPRECATED_PLUGINS
-        )
-
         # `keys_to_check` are the ProjectOption keys that tell us if a plugin is enabled (e.g. `plugin:enabled`) or are
         # configured properly, meaning they have the required information - plugin.required_field - needed for the
         # plugin to work (ex:`opsgenie:api_key`)
@@ -89,7 +84,7 @@ class OrganizationPluginsConfigsEndpoint(OrganizationEndpoint):
         serialized_plugins = []
         for plugin in desired_plugins:
             serialized_plugin = serialize(plugin, request.user, PluginSerializer())
-            if serialized_plugin["isDeprecated"] and serialized_plugin["isHidden"]:
+            if serialized_plugin["isDeprecated"]:
                 continue
 
             serialized_plugin["projectList"] = []
@@ -121,8 +116,7 @@ class OrganizationPluginsConfigsEndpoint(OrganizationEndpoint):
             serialized_plugin["projectList"].sort(key=lambda x: x["projectSlug"])
             serialized_plugins.append(serialized_plugin)
 
-        # Organization does have features to override shadow deprecation
-        if not serialized_plugins and is_shadow_deprecated_detailed_view:
+        if not serialized_plugins:
             raise Http404
 
         return Response(serialized_plugins)

+ 1 - 1
src/sentry/api/endpoints/project_plugin_details.py

@@ -40,7 +40,7 @@ class ProjectPluginDetailsEndpoint(ProjectEndpoint):
             context["config_error"] = str(e)
             context["auth_url"] = reverse("socialauth_associate", args=[plugin.slug])
 
-        if context["isDeprecated"] and context["isHidden"]:
+        if context["isDeprecated"]:
             raise Http404
         return Response(context)
 

+ 20 - 25
src/sentry/api/serializers/models/plugin.py

@@ -1,3 +1,5 @@
+from datetime import datetime
+
 from django.utils.text import slugify
 
 from sentry import features
@@ -14,8 +16,19 @@ SHADOW_DEPRECATED_PLUGINS = {
 
 
 def is_plugin_deprecated(plugin, project: Project) -> bool:
-    return plugin.slug in SHADOW_DEPRECATED_PLUGINS and not features.has(
-        SHADOW_DEPRECATED_PLUGINS.get(plugin.slug), getattr(project, "organization", None)
+    """
+    Determines whether or not a plugin has been deprecated.
+    If it is past the `deprecation_date` this will always be True.
+    If not, it checks the `SHADOW_DEPRECATED_PLUGINS` map and will return True only if
+    the plugin slug is present and the organization doesn't have the override feature.
+    """
+    deprecation_date = getattr(plugin, "deprecation_date", None)
+    is_past_deprecation_date = datetime.today() > deprecation_date if deprecation_date else False
+    return is_past_deprecation_date or (
+        plugin.slug in SHADOW_DEPRECATED_PLUGINS
+        and not features.has(
+            SHADOW_DEPRECATED_PLUGINS.get(plugin.slug), getattr(project, "organization", None)
+        )
     )
 
 
@@ -77,18 +90,9 @@ class PluginSerializer(Serializer):
         if obj.author:
             d["author"] = {"name": str(obj.author), "url": str(obj.author_url)}
 
-        # TODO(Leander): Convert this field to:
-        # d["isDeprecated"] = obj.slug in SHADOW_DEPRECATED_PLUGINS or datetime.today() > deprecation_date
-        # but we are late on deprecating right now
-        d["isDeprecated"] = obj.slug in SHADOW_DEPRECATED_PLUGINS
-
-        d["isHidden"] = (
-            not features.has(
-                SHADOW_DEPRECATED_PLUGINS.get(obj.slug), getattr(self.project, "organization", None)
-            )
-            if d["isDeprecated"]
-            else d.get("enabled", False) is False and obj.is_hidden()
-        )
+        d["isDeprecated"] = is_plugin_deprecated(obj, self.project)
+
+        d["isHidden"] = d["isDeprecated"] or (not d.get("enabled", False) and obj.is_hidden())
 
         if obj.description:
             d["description"] = str(obj.description)
@@ -136,19 +140,10 @@ def serialize_field(project, plugin, field):
         "readonly": field.get("readonly", False),
         "defaultValue": field.get("default"),
         "value": None,
-        # TODO(Leander): Convert this field to:
-        # "isDeprecated": obj.slug in SHADOW_DEPRECATED_PLUGINS or datetime.today() > deprecation_date
-        # but we are late on deprecating right now
-        "isDeprecated": plugin.slug in SHADOW_DEPRECATED_PLUGINS,
+        "isDeprecated": is_plugin_deprecated(plugin, project),
     }
 
-    data["isHidden"] = (
-        not features.has(
-            SHADOW_DEPRECATED_PLUGINS.get(plugin.slug), getattr(project, "organization", None)
-        )
-        if data["isDeprecated"]
-        else plugin.is_hidden()
-    )
+    data["isHidden"] = data["isDeprecated"] or plugin.is_hidden()
     if field.get("type") != "secret":
         data["value"] = plugin.get_option(field["name"], project)
     else:

+ 3 - 6
src/sentry/web/frontend/group_plugin_action.py

@@ -2,8 +2,7 @@ from django.http import Http404, HttpResponseRedirect
 from django.shortcuts import get_object_or_404
 from django.utils.http import is_safe_url
 
-from sentry import features
-from sentry.api.serializers.models.plugin import SHADOW_DEPRECATED_PLUGINS
+from sentry.api.serializers.models.plugin import is_plugin_deprecated
 from sentry.models import Group, GroupMeta
 from sentry.plugins.base import plugins
 from sentry.web.frontend.base import ProjectView
@@ -16,11 +15,9 @@ class GroupPluginActionView(ProjectView):
         group = get_object_or_404(Group, pk=group_id, project=project)
 
         try:
-            if slug in SHADOW_DEPRECATED_PLUGINS and not features.has(
-                SHADOW_DEPRECATED_PLUGINS.get(slug), getattr(project, "organization", None)
-            ):
-                raise Http404("Plugin not found")
             plugin = plugins.get(slug)
+            if is_plugin_deprecated(plugin, project):
+                raise Http404("Plugin not found")
         except KeyError:
             raise Http404("Plugin not found")
 

+ 1 - 0
static/app/types/integrations.tsx

@@ -394,6 +394,7 @@ export type PluginNoProject = {
   features: string[];
   featureDescriptions: IntegrationFeature[];
   isHidden: boolean;
+  isDeprecated: boolean;
   version?: string;
   author?: {name: string; url: string};
   description?: string;

+ 1 - 13
static/app/views/settings/project/navigationConfiguration.tsx

@@ -10,17 +10,6 @@ type ConfigParams = {
 
 const pathPrefix = '/settings/:orgId/projects/:projectId';
 
-// Object with the pluginId as the key, and enablingFeature as the value
-const SHADOW_DEPRECATED_PLUGINS = {};
-
-const canViewPlugin = (pluginId: string, organization?: Organization) => {
-  const isDeprecated = SHADOW_DEPRECATED_PLUGINS.hasOwnProperty(pluginId);
-  const hasFeature = organization?.features?.includes(
-    SHADOW_DEPRECATED_PLUGINS[pluginId]
-  );
-  return isDeprecated ? hasFeature : true;
-};
-
 export default function getConfiguration({
   project,
   organization,
@@ -171,8 +160,7 @@ export default function getConfiguration({
         ...plugins.map(plugin => ({
           path: `${pathPrefix}/plugins/${plugin.id}/`,
           title: plugin.name,
-          show: opts =>
-            opts?.access?.has('project:write') && canViewPlugin(plugin.id, organization),
+          show: opts => opts?.access?.has('project:write') && !plugin.isDeprecated,
           id: 'plugin_details',
           recordAnalytics: true,
         })),