Skip to content

Commit 1ea5f18

Browse files
committed
Add API endpoints to expose information about registered app group lifecycle plugins to power frontend components writing config and reading config and status for plugins.
1 parent ae1a9fa commit 1ea5f18

File tree

6 files changed

+232
-0
lines changed

6 files changed

+232
-0
lines changed

api/app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
exception_views,
2929
groups_views,
3030
health_check_views,
31+
plugins_views,
3132
role_requests_views,
3233
roles_views,
3334
tags_views,
@@ -263,5 +264,7 @@ def add_headers(response: Response) -> ResponseReturnValue:
263264
tags_views.register_docs()
264265
app.register_blueprint(webhook_views.bp)
265266
app.register_blueprint(bugs_views.bp)
267+
app.register_blueprint(plugins_views.bp)
268+
plugins_views.register_docs()
266269

267270
return app

api/views/plugins_views.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from flask import Blueprint
2+
3+
from api.extensions import Api, docs
4+
from api.views.resources import (
5+
AppGroupLifecyclePluginAppConfigProperties,
6+
AppGroupLifecyclePluginAppStatusProperties,
7+
AppGroupLifecyclePluginGroupConfigProperties,
8+
AppGroupLifecyclePluginGroupStatusProperties,
9+
AppGroupLifecyclePluginList,
10+
)
11+
12+
bp_name = "api-plugins"
13+
bp_url_prefix = "/api/plugins"
14+
bp = Blueprint(bp_name, __name__, url_prefix=bp_url_prefix)
15+
16+
api = Api(bp)
17+
18+
api.add_resource(AppGroupLifecyclePluginList, "/app-group-lifecycle", endpoint="app_group_lifecycle_plugins")
19+
api.add_resource(
20+
AppGroupLifecyclePluginAppConfigProperties,
21+
"/app-group-lifecycle/<string:plugin_id>/app-config-props",
22+
endpoint="app_group_lifecycle_plugin_app_config_props",
23+
)
24+
api.add_resource(
25+
AppGroupLifecyclePluginGroupConfigProperties,
26+
"/app-group-lifecycle/<string:plugin_id>/group-config-props",
27+
endpoint="app_group_lifecycle_plugin_group_config_props",
28+
)
29+
api.add_resource(
30+
AppGroupLifecyclePluginAppStatusProperties,
31+
"/app-group-lifecycle/<string:plugin_id>/app-status-props",
32+
endpoint="app_group_lifecycle_plugin_app_status_props",
33+
)
34+
api.add_resource(
35+
AppGroupLifecyclePluginGroupStatusProperties,
36+
"/app-group-lifecycle/<string:plugin_id>/group-status-props",
37+
endpoint="app_group_lifecycle_plugin_group_status_props",
38+
)
39+
40+
41+
def register_docs() -> None:
42+
docs.register(AppGroupLifecyclePluginList, blueprint=bp_name, endpoint="app_group_lifecycle_plugins")
43+
docs.register(
44+
AppGroupLifecyclePluginAppConfigProperties,
45+
blueprint=bp_name,
46+
endpoint="app_group_lifecycle_plugin_app_config_props",
47+
)
48+
docs.register(
49+
AppGroupLifecyclePluginGroupConfigProperties,
50+
blueprint=bp_name,
51+
endpoint="app_group_lifecycle_plugin_group_config_props",
52+
)
53+
docs.register(
54+
AppGroupLifecyclePluginAppStatusProperties,
55+
blueprint=bp_name,
56+
endpoint="app_group_lifecycle_plugin_app_status_props",
57+
)
58+
docs.register(
59+
AppGroupLifecyclePluginGroupStatusProperties,
60+
blueprint=bp_name,
61+
endpoint="app_group_lifecycle_plugin_group_status_props",
62+
)

api/views/resources/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
from api.views.resources.audit import GroupRoleAuditResource, UserGroupAuditResource
44
from api.views.resources.bug import SentryProxyResource
55
from api.views.resources.group import GroupAuditResource, GroupList, GroupMemberResource, GroupResource
6+
from api.views.resources.plugin import (
7+
AppGroupLifecyclePluginAppConfigProperties,
8+
AppGroupLifecyclePluginAppStatusProperties,
9+
AppGroupLifecyclePluginGroupConfigProperties,
10+
AppGroupLifecyclePluginGroupStatusProperties,
11+
AppGroupLifecyclePluginList,
12+
)
613
from api.views.resources.role import RoleAuditResource, RoleList, RoleMemberResource, RoleResource
714
from api.views.resources.role_request import RoleRequestList, RoleRequestResource
815
from api.views.resources.tag import TagList, TagResource
@@ -12,6 +19,11 @@
1219
__all__ = [
1320
"AccessRequestList",
1421
"AccessRequestResource",
22+
"AppGroupLifecyclePluginAppConfigProperties",
23+
"AppGroupLifecyclePluginAppStatusProperties",
24+
"AppGroupLifecyclePluginGroupConfigProperties",
25+
"AppGroupLifecyclePluginGroupStatusProperties",
26+
"AppGroupLifecyclePluginList",
1527
"AppList",
1628
"AppResource",
1729
"GroupAuditResource",

api/views/resources/plugin.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from dataclasses import asdict
2+
3+
from flask.typing import ResponseReturnValue
4+
from flask_apispec import MethodResource
5+
6+
from api.apispec import FlaskApiSpecDecorators
7+
from api.plugins.app_group_lifecycle import (
8+
get_app_group_lifecycle_plugin_app_config_properties,
9+
get_app_group_lifecycle_plugin_app_status_properties,
10+
get_app_group_lifecycle_plugin_group_config_properties,
11+
get_app_group_lifecycle_plugin_group_status_properties,
12+
get_app_group_lifecycle_plugins,
13+
)
14+
from api.views.schemas import (
15+
AppGroupLifecyclePluginConfigPropertySchema,
16+
AppGroupLifecyclePluginMetadataSchema,
17+
AppGroupLifecyclePluginStatusPropertySchema,
18+
)
19+
20+
21+
class AppGroupLifecyclePluginList(MethodResource):
22+
"""Resource for listing available app group lifecycle plugins."""
23+
24+
@FlaskApiSpecDecorators.response_schema(AppGroupLifecyclePluginMetadataSchema)
25+
def get(self) -> ResponseReturnValue:
26+
"""
27+
Get a list of all available app group lifecycle plugins.
28+
29+
Returns:
30+
A list of plugin metadata objects.
31+
"""
32+
plugins = get_app_group_lifecycle_plugins()
33+
# Sort by display name alphabetically (ascending)
34+
plugins = sorted(plugins, key=lambda p: p.display_name.lower())
35+
return [asdict(plugin) for plugin in plugins], 200
36+
37+
38+
class AppGroupLifecyclePluginAppConfigProperties(MethodResource):
39+
"""Resource for getting app-level configuration properties for a specific plugin."""
40+
41+
@FlaskApiSpecDecorators.response_schema(AppGroupLifecyclePluginConfigPropertySchema)
42+
def get(self, plugin_id: str) -> ResponseReturnValue:
43+
"""
44+
Get app-level configuration properties for a specific plugin.
45+
46+
Args:
47+
plugin_id: The ID of the plugin
48+
49+
Returns:
50+
Dictionary mapping property names to property schemas
51+
"""
52+
# Verify the plugin is registered
53+
plugins = [plugin.id for plugin in get_app_group_lifecycle_plugins()]
54+
if plugin_id not in plugins:
55+
return {"error": f"Plugin '{plugin_id}' not found"}, 404
56+
57+
config_properties = get_app_group_lifecycle_plugin_app_config_properties(plugin_id)
58+
return {name: asdict(schema) for name, schema in config_properties.items()}, 200
59+
60+
61+
class AppGroupLifecyclePluginGroupConfigProperties(MethodResource):
62+
"""Resource for getting group-level configuration properties for a specific plugin."""
63+
64+
@FlaskApiSpecDecorators.response_schema(AppGroupLifecyclePluginConfigPropertySchema)
65+
def get(self, plugin_id: str) -> ResponseReturnValue:
66+
"""
67+
Get group-level configuration properties for a specific plugin.
68+
69+
Args:
70+
plugin_id: The ID of the plugin
71+
72+
Returns:
73+
Dictionary mapping property names to property schemas
74+
"""
75+
# Verify the plugin is registered
76+
plugins = [plugin.id for plugin in get_app_group_lifecycle_plugins()]
77+
if plugin_id not in plugins:
78+
return {"error": f"Plugin '{plugin_id}' not found"}, 404
79+
80+
config_properties = get_app_group_lifecycle_plugin_group_config_properties(plugin_id)
81+
return {name: asdict(schema) for name, schema in config_properties.items()}, 200
82+
83+
84+
class AppGroupLifecyclePluginAppStatusProperties(MethodResource):
85+
"""Resource for getting app-level status properties for a specific plugin."""
86+
87+
@FlaskApiSpecDecorators.response_schema(AppGroupLifecyclePluginStatusPropertySchema)
88+
def get(self, plugin_id: str) -> ResponseReturnValue:
89+
"""
90+
Get app-level status properties for a specific plugin.
91+
92+
Args:
93+
plugin_id: The ID of the plugin
94+
95+
Returns:
96+
Dictionary mapping property names to property schemas
97+
"""
98+
# Verify the plugin is registered
99+
plugins = [plugin.id for plugin in get_app_group_lifecycle_plugins()]
100+
if plugin_id not in plugins:
101+
return {"error": f"Plugin '{plugin_id}' not found"}, 404
102+
103+
status_properties = get_app_group_lifecycle_plugin_app_status_properties(plugin_id)
104+
return {name: asdict(schema) for name, schema in status_properties.items()}, 200
105+
106+
107+
class AppGroupLifecyclePluginGroupStatusProperties(MethodResource):
108+
"""Resource for getting group-level status properties for a specific plugin."""
109+
110+
@FlaskApiSpecDecorators.response_schema(AppGroupLifecyclePluginStatusPropertySchema)
111+
def get(self, plugin_id: str) -> ResponseReturnValue:
112+
"""
113+
Get group-level status properties for a specific plugin.
114+
115+
Args:
116+
plugin_id: The ID of the plugin
117+
118+
Returns:
119+
Dictionary mapping property names to property schemas
120+
"""
121+
# Verify the plugin is registered
122+
plugins = [plugin.id for plugin in get_app_group_lifecycle_plugins()]
123+
if plugin_id not in plugins:
124+
return {"error": f"Plugin '{plugin_id}' not found"}, 404
125+
126+
status_properties = get_app_group_lifecycle_plugin_group_status_properties(plugin_id)
127+
return {name: asdict(schema) for name, schema in status_properties.items()}, 200

api/views/schemas/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
from api.views.schemas.audit_logs import AuditLogSchema, EventType
66
from api.views.schemas.core_schemas import (
77
AccessRequestSchema,
8+
AppGroupLifecyclePluginConfigPropertySchema,
9+
AppGroupLifecyclePluginMetadataSchema,
10+
AppGroupLifecyclePluginStatusPropertySchema,
811
AppGroupSchema,
912
AppSchema,
1013
AppTagMapSchema,
@@ -47,6 +50,9 @@
4750
__all__ = [
4851
"AccessRequestPaginationSchema",
4952
"AccessRequestSchema",
53+
"AppGroupLifecyclePluginConfigPropertySchema",
54+
"AppGroupLifecyclePluginMetadataSchema",
55+
"AppGroupLifecyclePluginStatusPropertySchema",
5056
"AppGroupSchema",
5157
"AppPaginationSchema",
5258
"AppSchema",

api/views/schemas/core_schemas.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,3 +1768,25 @@ class Meta:
17681768
"group_tag_mapping",
17691769
"active_group_tag_mappings",
17701770
)
1771+
1772+
1773+
# Plugin-related schemas
1774+
class AppGroupLifecyclePluginMetadataSchema(Schema):
1775+
id = fields.String(required=True)
1776+
display_name = fields.String(required=True)
1777+
description = fields.String(required=False, allow_none=True)
1778+
1779+
1780+
class AppGroupLifecyclePluginConfigPropertySchema(Schema):
1781+
display_name = fields.String(required=True)
1782+
help_text = fields.String(required=False, allow_none=True)
1783+
type = fields.String(required=True, validate=validate.OneOf(["text", "number", "boolean"]))
1784+
default_value = fields.Raw(required=False, allow_none=True)
1785+
required = fields.Boolean(required=False, load_default=False)
1786+
validation = fields.Dict(required=False, allow_none=True)
1787+
1788+
1789+
class AppGroupLifecyclePluginStatusPropertySchema(Schema):
1790+
display_name = fields.String(required=True)
1791+
help_text = fields.String(required=False, allow_none=True)
1792+
type = fields.String(required=True, validate=validate.OneOf(["text", "number", "date", "boolean"]))

0 commit comments

Comments
 (0)