Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref(teamwork): Shadow deprecate teamwork plugin #29052

Merged
merged 8 commits into from
Oct 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/sentry/api/endpoints/group_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.utils import timezone
from rest_framework.response import Response

from sentry import tagstore, tsdb
from sentry import features, tagstore, tsdb
from sentry.api import client
from sentry.api.base import EnvironmentMixin
from sentry.api.bases import GroupEndpoint
Expand All @@ -18,7 +18,7 @@
update_groups,
)
from sentry.api.serializers import GroupSerializer, GroupSerializerSnuba, serialize
from sentry.api.serializers.models.plugin import PluginSerializer
from sentry.api.serializers.models.plugin import SHADOW_DEPRECATED_PLUGINS, PluginSerializer
from sentry.models import Activity, Group, GroupSeen, GroupSubscriptionManager, UserReport
from sentry.models.groupinbox import get_inbox_details
from sentry.plugins.base import plugins
Expand All @@ -44,6 +44,11 @@ def _get_actions(self, request, group):

action_list = []
for plugin in plugins.for_project(project, version=1):
if plugin.slug in SHADOW_DEPRECATED_PLUGINS and not features.has(
SHADOW_DEPRECATED_PLUGINS.get(plugin.slug), getattr(project, "organization", None)
):
continue

results = safe_execute(
plugin.actions, request, group, action_list, _with_transaction=False
)
Expand All @@ -54,6 +59,10 @@ def _get_actions(self, request, group):
action_list = results

for plugin in plugins.for_project(project, version=2):
if plugin.slug in SHADOW_DEPRECATED_PLUGINS and not features.has(
SHADOW_DEPRECATED_PLUGINS.get(plugin.slug), getattr(project, "organization", None)
):
continue
for action in (
safe_execute(plugin.get_actions, request, group, _with_transaction=False) or ()
):
Expand Down
14 changes: 13 additions & 1 deletion src/sentry/api/endpoints/organization_plugins_configs.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from django.http.response import Http404
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 PluginSerializer
from sentry.api.serializers.models.plugin import SHADOW_DEPRECATED_PLUGINS, PluginSerializer
from sentry.constants import ObjectStatus
from sentry.models import Project, ProjectOption
from sentry.plugins.base import plugins
Expand Down Expand Up @@ -33,6 +34,11 @@ def get(self, request, organization):
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`)
Expand Down Expand Up @@ -83,6 +89,8 @@ def get(self, request, organization):
serialized_plugins = []
for plugin in desired_plugins:
serialized_plugin = serialize(plugin, request.user, PluginSerializer())
if serialized_plugin["isDeprecated"] and serialized_plugin["isHidden"]:
continue

serialized_plugin["projectList"] = []

Expand Down Expand Up @@ -113,4 +121,8 @@ def get(self, request, organization):
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:
raise Http404

return Response(serialized_plugins)
3 changes: 3 additions & 0 deletions src/sentry/api/endpoints/project_plugin_details.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django import forms
from django.http.response import Http404
from django.urls import reverse
from rest_framework import serializers
from rest_framework.response import Response
Expand Down Expand Up @@ -39,6 +40,8 @@ def get(self, request, project, plugin_id):
context["config_error"] = str(e)
context["auth_url"] = reverse("socialauth_associate", args=[plugin.slug])

if context["isDeprecated"] and context["isHidden"]:
raise Http404
return Response(context)

def post(self, request, project, plugin_id):
Expand Down
29 changes: 28 additions & 1 deletion src/sentry/api/serializers/models/plugin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from django.utils.text import slugify

from sentry import features
from sentry.api.serializers import Serializer
from sentry.models import ProjectOption
from sentry.utils.assets import get_asset_url
from sentry.utils.http import absolute_uri

# Dict with the plugin_name as the key, and enabling_feature_name as the value
SHADOW_DEPRECATED_PLUGINS = {"teamwork": "organizations:integrations-ignore-teamwork-deprecation"}


class PluginSerializer(Serializer):
def __init__(self, project=None):
Expand Down Expand Up @@ -64,7 +68,18 @@ def serialize(self, obj, attrs, user):
if obj.author:
d["author"] = {"name": str(obj.author), "url": str(obj.author_url)}

d["isHidden"] = d.get("enabled", False) is False and obj.is_hidden()
# 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()
)

if obj.description:
d["description"] = str(obj.description)
Expand Down Expand Up @@ -112,7 +127,19 @@ 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,
}

data["isHidden"] = (
not features.has(
SHADOW_DEPRECATED_PLUGINS.get(plugin.slug), getattr(project, "organization", None)
)
if data["isDeprecated"]
else plugin.is_hidden()
)
if field.get("type") != "secret":
data["value"] = plugin.get_option(field["name"], project)
else:
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/conf/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,8 @@ def create_partitioned_queues(name):
"organizations:integrations-stacktrace-link": False,
# Allow orgs to install a custom source code management integration
"organizations:integrations-custom-scm": False,
# Allow orgs to view the Teamwork plugin
"organizations:integrations-ignore-teamwork-deprecation": False,
# Allow orgs to debug internal/unpublished sentry apps with logging
"organizations:sentry-app-debugging": False,
# Temporary safety measure, turned on for specific orgs only if
Expand Down
3 changes: 3 additions & 0 deletions src/sentry/features/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@
default_manager.add("organizations:integrations-stacktrace-link", OrganizationFeature)
default_manager.add("organizations:integrations-ticket-rules", OrganizationFeature, True)
default_manager.add("organizations:integrations-vsts-limited-scopes", OrganizationFeature)
default_manager.add(
"organizations:integrations-ignore-teamwork-deprecation", OrganizationFeature, True
)
default_manager.add("organizations:invite-members", OrganizationFeature)
default_manager.add("organizations:invite-members-rate-limits", OrganizationFeature)
default_manager.add("organizations:issue-list-trend-sort", OrganizationFeature, True)
Expand Down
6 changes: 6 additions & 0 deletions src/sentry/web/frontend/group_plugin_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
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.models import Group, GroupMeta
from sentry.plugins.base import plugins
from sentry.web.frontend.base import ProjectView
Expand All @@ -14,6 +16,10 @@ def handle(self, request, organization, project, group_id, slug):
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)
except KeyError:
raise Http404("Plugin not found")
Expand Down
16 changes: 15 additions & 1 deletion static/app/views/settings/project/navigationConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ type ConfigParams = {

const pathPrefix = '/settings/:orgId/projects/:projectId';

// Object with the pluginId as the key, and enablingFeature as the value
const SHADOW_DEPRECATED_PLUGINS = {
teamwork: 'integrations-ignore-teamwork-deprecation',
};

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,
Expand Down Expand Up @@ -160,7 +173,8 @@ export default function getConfiguration({
...plugins.map(plugin => ({
path: `${pathPrefix}/plugins/${plugin.id}/`,
title: plugin.name,
show: opts => opts?.access?.has('project:write'),
show: opts =>
opts?.access?.has('project:write') && canViewPlugin(plugin.id, organization),
id: 'plugin_details',
recordAnalytics: true,
})),
Expand Down
Loading