Skip to content

Commit 8b3dbfe

Browse files
committed
feat(data_classes): add CloudWatch dashboard custom widget event
1 parent a45a792 commit 8b3dbfe

File tree

5 files changed

+266
-0
lines changed

5 files changed

+266
-0
lines changed

aws_lambda_powertools/utilities/data_classes/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .alb_event import ALBEvent
66
from .api_gateway_proxy_event import APIGatewayProxyEvent, APIGatewayProxyEventV2
77
from .appsync_resolver_event import AppSyncResolverEvent
8+
from .cloud_watch_custom_widget_event import CloudWatchDashboardCustomWidgetEvent
89
from .cloud_watch_logs_event import CloudWatchLogsEvent
910
from .code_pipeline_job_event import CodePipelineJobEvent
1011
from .connect_contact_flow_event import ConnectContactFlowEvent
@@ -23,6 +24,7 @@
2324
"APIGatewayProxyEventV2",
2425
"AppSyncResolverEvent",
2526
"ALBEvent",
27+
"CloudWatchDashboardCustomWidgetEvent",
2628
"CloudWatchLogsEvent",
2729
"CodePipelineJobEvent",
2830
"ConnectContactFlowEvent",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
from typing import Any, Dict, Optional
2+
3+
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
4+
5+
6+
class TimeZone(DictWrapper):
7+
@property
8+
def label(self) -> str:
9+
"""The time range label. Either 'UTC' or 'Local'"""
10+
return self["label"]
11+
12+
@property
13+
def offset_iso(self) -> str:
14+
"""The time range offset in the format +/-00:00"""
15+
return self["offsetISO"]
16+
17+
@property
18+
def offset_in_minutes(self) -> int:
19+
"""The time range offset in minutes"""
20+
return int(self["offsetInMinutes"])
21+
22+
23+
class TimeRange(DictWrapper):
24+
@property
25+
def mode(self) -> str:
26+
"""The time range mode, i.e. 'relative' or 'absolute'"""
27+
return self["mode"]
28+
29+
@property
30+
def start(self) -> int:
31+
"""The start time within the time range"""
32+
return self["start"]
33+
34+
@property
35+
def end(self) -> int:
36+
"""The end time within the time range"""
37+
return self["end"]
38+
39+
@property
40+
def relative_start(self) -> Optional[int]:
41+
"""The relative start time within the time range"""
42+
return self.get("relativeStart")
43+
44+
@property
45+
def zoom_start(self) -> Optional[int]:
46+
"""The start time within the zoomed time range"""
47+
return (self.get("zoom") or {}).get("start")
48+
49+
@property
50+
def zoom_end(self) -> Optional[int]:
51+
"""The end time within the zoomed time range"""
52+
return (self.get("zoom") or {}).get("end")
53+
54+
55+
class CloudWatchWidgetContext(DictWrapper):
56+
@property
57+
def dashboard_name(self) -> str:
58+
"""Get dashboard name, in which the widget is used"""
59+
return self["dashboardName"]
60+
61+
@property
62+
def widget_id(self) -> str:
63+
"""Get widget ID"""
64+
return self["widgetId"]
65+
66+
@property
67+
def account_id(self) -> str:
68+
"""Get AWS Account ID"""
69+
return self["accountId"]
70+
71+
@property
72+
def locale(self) -> str:
73+
"""Get locale language"""
74+
return self["locale"]
75+
76+
@property
77+
def timezone(self) -> TimeZone:
78+
"""Timezone information of the dashboard"""
79+
return TimeZone(self["timezone"])
80+
81+
@property
82+
def period(self) -> int:
83+
"""The period shown on the dashboard"""
84+
return int(self["period"])
85+
86+
@property
87+
def is_auto_period(self) -> bool:
88+
"""Whether auto period is enabled"""
89+
return bool(self["isAutoPeriod"])
90+
91+
@property
92+
def time_range(self) -> TimeRange:
93+
"""The widget time range"""
94+
return TimeRange(self["timeRange"])
95+
96+
@property
97+
def theme(self) -> str:
98+
"""The dashboard theme, i.e. 'light' or 'dark'"""
99+
return self["theme"]
100+
101+
@property
102+
def link_charts(self) -> bool:
103+
"""The widget is linked to other charts"""
104+
return bool(self["linkCharts"])
105+
106+
@property
107+
def title(self) -> str:
108+
"""Get widget title"""
109+
return self["title"]
110+
111+
@property
112+
def params(self) -> Dict[str, Any]:
113+
"""Get widget parameters"""
114+
return self["params"]
115+
116+
@property
117+
def forms(self) -> Dict[str, Any]:
118+
"""Get widget form data"""
119+
return self["forms"]["all"]
120+
121+
@property
122+
def height(self) -> int:
123+
"""Get widget height"""
124+
return int(self["height"])
125+
126+
@property
127+
def width(self) -> int:
128+
"""Get widget width"""
129+
return int(self["width"])
130+
131+
132+
class CloudWatchDashboardCustomWidgetEvent(DictWrapper):
133+
"""CloudWatch dashboard custom widget event
134+
135+
You can use a Lambda function to create a custom widget on a CloudWatch dashboard.
136+
137+
Documentation:
138+
-------------
139+
- https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/add_custom_widget_dashboard_about.html
140+
"""
141+
142+
@property
143+
def describe(self) -> bool:
144+
"""Display widget documentation"""
145+
return bool(self.get("describe", False))
146+
147+
@property
148+
def widget_context(self) -> Optional[CloudWatchWidgetContext]:
149+
"""The widget context"""
150+
if self.get("widgetContext"):
151+
return CloudWatchWidgetContext(self["widgetContext"])
152+
153+
return None

docs/utilities/data_classes.md

+36
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Event Source | Data_class
6868
[Application Load Balancer](#application-load-balancer) | `ALBEvent`
6969
[AppSync Authorizer](#appsync-authorizer) | `AppSyncAuthorizerEvent`
7070
[AppSync Resolver](#appsync-resolver) | `AppSyncResolverEvent`
71+
[CloudWatch Dashboard Custom Widget](#cloudwatch-dashboard-custom-widget) | `CloudWatchDashboardCustomWidgetEvent`
7172
[CloudWatch Logs](#cloudwatch-logs) | `CloudWatchLogsEvent`
7273
[CodePipeline Job Event](#codepipeline-job) | `CodePipelineJobEvent`
7374
[Cognito User Pool](#cognito-user-pool) | Multiple available under `cognito_user_pool_event`
@@ -441,6 +442,41 @@ In this example, we also use the new Logger `correlation_id` and built-in `corre
441442
}
442443
```
443444

445+
### CloudWatch Dashboard Custom Widget
446+
447+
=== "app.py"
448+
449+
```python
450+
from aws_lambda_powertools.utilities.data_classes import event_source, CloudWatchDashboardCustomWidgetEvent
451+
from aws_lambda_powertools.utilities.data_classes.cloud_watch_logs_event import CloudWatchLogsDecodedData
452+
453+
const DOCS = `
454+
## Echo
455+
A simple echo script. Anything passed in \`\`\`echo\`\`\` parameter is returned as the content of custom widget.
456+
457+
### Widget parameters
458+
Param | Description
459+
---|---
460+
**echo** | The content to echo back
461+
462+
### Example parameters
463+
\`\`\` yaml
464+
echo: <h1>Hello world</h1>
465+
\`\`\`
466+
`
467+
468+
@event_source(data_class=CloudWatchDashboardCustomWidgetEvent)
469+
def lambda_handler(event: CloudWatchDashboardCustomWidgetEvent, context):
470+
471+
if event.describe:
472+
return DOCS
473+
474+
# alternatively you can also do
475+
# event.widget_context.params["echo"]
476+
# event.raw_event["echo"]
477+
return event["echo"]
478+
```
479+
444480
### CloudWatch Logs
445481

446482
CloudWatch Logs events by default are compressed and base64 encoded. You can use the helper function provided to decode,
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"original": "param-to-widget",
3+
"widgetContext": {
4+
"dashboardName": "Name-of-current-dashboard",
5+
"widgetId": "widget-16",
6+
"accountId": "123456789123",
7+
"locale": "en",
8+
"timezone": {
9+
"label": "UTC",
10+
"offsetISO": "+00:00",
11+
"offsetInMinutes": 0
12+
},
13+
"period": 300,
14+
"isAutoPeriod": true,
15+
"timeRange": {
16+
"mode": "relative",
17+
"start": 1627236199729,
18+
"end": 1627322599729,
19+
"relativeStart": 86400012,
20+
"zoom": {
21+
"start": 1627276030434,
22+
"end": 1627282956521
23+
}
24+
},
25+
"theme": "light",
26+
"linkCharts": true,
27+
"title": "Tweets for Amazon website problem",
28+
"forms": {
29+
"all": {}
30+
},
31+
"params": {
32+
"original": "param-to-widget"
33+
},
34+
"width": 588,
35+
"height": 369
36+
}
37+
}

tests/functional/test_data_classes.py

+38
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
APIGatewayProxyEvent,
1414
APIGatewayProxyEventV2,
1515
AppSyncResolverEvent,
16+
CloudWatchDashboardCustomWidgetEvent,
1617
CloudWatchLogsEvent,
1718
CodePipelineJobEvent,
1819
EventBridgeEvent,
@@ -99,6 +100,43 @@ def message(self) -> str:
99100
assert DataClassSample(data1).raw_event is data1
100101

101102

103+
def test_cloud_watch_dashboard_event():
104+
event = CloudWatchDashboardCustomWidgetEvent(load_event("cloudWatchDashboardEvent.json"))
105+
assert event.describe is False
106+
assert event.widget_context.account_id == "123456789123"
107+
assert event.widget_context.dashboard_name == "Name-of-current-dashboard"
108+
assert event.widget_context.widget_id == "widget-16"
109+
assert event.widget_context.locale == "en"
110+
assert event.widget_context.timezone.label == "UTC"
111+
assert event.widget_context.timezone.offset_iso == "+00:00"
112+
assert event.widget_context.timezone.offset_in_minutes == 0
113+
assert event.widget_context.period == 300
114+
assert event.widget_context.is_auto_period is True
115+
assert event.widget_context.time_range.mode == "relative"
116+
assert event.widget_context.time_range.start == 1627236199729
117+
assert event.widget_context.time_range.end == 1627322599729
118+
assert event.widget_context.time_range.relative_start == 86400012
119+
assert event.widget_context.time_range.zoom_start == 1627276030434
120+
assert event.widget_context.time_range.zoom_end == 1627282956521
121+
assert event.widget_context.theme == "light"
122+
assert event.widget_context.link_charts is True
123+
assert event.widget_context.title == "Tweets for Amazon website problem"
124+
assert event.widget_context.forms == {}
125+
assert event.widget_context.params == {"original": "param-to-widget"}
126+
assert event.widget_context.width == 588
127+
assert event.widget_context.height == 369
128+
assert event.widget_context.params["original"] == "param-to-widget"
129+
assert event["original"] == "param-to-widget"
130+
assert event.raw_event["original"] == "param-to-widget"
131+
132+
133+
def test_cloud_watch_dashboard_describe_event():
134+
event = CloudWatchDashboardCustomWidgetEvent({"describe": True})
135+
assert event.describe is True
136+
assert event.widget_context is None
137+
assert event.raw_event == {"describe": True}
138+
139+
102140
def test_cloud_watch_trigger_event():
103141
event = CloudWatchLogsEvent(load_event("cloudWatchLogEvent.json"))
104142

0 commit comments

Comments
 (0)