Skip to content

Commit d118a6e

Browse files
Eugene Cheungdjdawson3
andauthored
feat(dashboards)!: improve customizability (#355)
This reverts commit acd69cb, i.e. it reintroduces #349 by @djdawson3. However, this also has a major version bump to v4. BREAKING CHANGE: this introduces a new way of building dashboards which is mostly backwards compatible, but does expect that a new IDynamicDashboardFactory interface is implemented for a MonitoringFacade's dashboardFactory. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_ Co-authored-by: djdawson3 <[email protected]>
1 parent acd69cb commit d118a6e

File tree

13 files changed

+1569
-41
lines changed

13 files changed

+1569
-41
lines changed

.projen/tasks.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.projenrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const project = new awscdk.AwsCdkConstructLibrary({
1111
keywords: ["cloudwatch", "monitoring"],
1212

1313
defaultReleaseBranch: "main",
14-
majorVersion: 3,
14+
majorVersion: 4,
1515
stability: "experimental",
1616

1717
cdkVersion: CDK_VERSION,

API.md

Lines changed: 1068 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ In your `package.json`:
2626
```json
2727
{
2828
"dependencies": {
29-
"cdk-monitoring-constructs": "^3.0.0",
29+
"cdk-monitoring-constructs": "^4.0.0",
3030

3131
// peer dependencies of cdk-monitoring-constructs
3232
"@aws-cdk/aws-apigatewayv2-alpha": "^2.65.0-alpha.0",
@@ -265,6 +265,79 @@ This is a general procedure on how to do it:
265265

266266
Both of these monitoring base classes are dashboard segments, so you can add them to your monitoring by calling `.addSegment()` on the `MonitoringFacade`.
267267

268+
### Custom dashboards
269+
270+
If you want *even* more flexibility, you can take complete control over dashboard generation by leveraging dynamic dashboarding features. This allows you to create an arbitrary number of dashboards while configuring each of them separately. You can do this in three simple steps:
271+
272+
1. Create a dynamic dashboard factory
273+
2. Create `IDynamicDashboardSegment` implementations
274+
3. Add Dynamic Segments to your `MonitoringFacade`
275+
276+
#### Create a dynamic dashboard factory
277+
278+
The below code sample will generate two dashboards with the following names:
279+
* ExampleDashboards-HostedService
280+
* ExampleDashboards-Infrastructure
281+
282+
283+
```ts
284+
// create the dynamic dashboard factory.
285+
const factory = new DynamicDashboardFactory(stack, "DynamicDashboards", {
286+
dashboardNamePrefix: "ExampleDashboards",
287+
dashboardConfigs: [
288+
// 'name' is the minimum required configuration
289+
{ name: "HostedService" },
290+
// below is an example of additional dashboard-specific config options
291+
{
292+
name: "Infrastructure",
293+
range: Duration.hours(3),
294+
periodOverride: PeriodOverride.AUTO,
295+
renderingPreference: DashboardRenderingPreference.BITMAP_ONLY
296+
},
297+
],
298+
});
299+
```
300+
301+
#### Create `IDynamicDashboardSegment` implementations
302+
For each construct you want monitored, you will need to create an implementation of an `IDynamicDashboardSegment`. The following is a basic reference implementation as an example:
303+
304+
```ts
305+
export enum DashboardTypes {
306+
HostedService = "HostedService",
307+
Infrastructure = "Infrastructure",
308+
}
309+
310+
class ExampleSegment implements IDynamicDashboardSegment {
311+
widgetsForDashboard(name: string): IWidget[] {
312+
// this logic is what's responsible for allowing your dynamic segment to return
313+
// different widgets for different dashboards
314+
switch (name) {
315+
case DashboardTypes.HostedService:
316+
return [new TextWidget({ markdown: "This shows metrics for your service hosted on AWS Infrastructure" })];
317+
case DashboardTypes.Infrastructure:
318+
return [new TextWidget({ markdown: "This shows metrics for the AWS Infrastructure supporting your hosted service" })];
319+
default:
320+
throw new Error("Unexpected dashboard name!");
321+
}
322+
}
323+
}
324+
```
325+
326+
#### Add Dynamic Segments to MonitoringFacade
327+
328+
When you have instances of an `IDynamicDashboardSegment` to use, they can be added to your dashboard like this:
329+
330+
```ts
331+
monitoring.addDynamicSegment(new ExampleSegment());
332+
```
333+
334+
Now, this widget will be added to both dashboards and will show different content depending on the dashboard. Using the above example code, two dashboards will be generated with the following content:
335+
336+
* Dashboard Name: "ExampleDashboards-HostedService"
337+
* Content: "This shows metrics for your service hosted on AWS Infrastructure"
338+
* Dashboard Name: "ExampleDashboards-Infrastructure"
339+
* Content: "This shows metrics for the AWS Infrastructure supporting your hosted service"
340+
268341
### Monitoring scopes
269342

270343
You can monitor complete CDK construct scopes using an aspect. It will automatically discover all monitorable resources within the scope recursively and add them to your dashboard.

lib/common/monitoring/Monitoring.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { IWidget } from "aws-cdk-lib/aws-cloudwatch";
22

33
import { MonitoringScope } from "./MonitoringScope";
44
import {
5+
DefaultDashboards,
56
IDashboardSegment,
7+
IDynamicDashboardSegment,
68
MonitoringDashboardsOverrideProps,
79
UserProvidedNames,
810
} from "../../dashboard";
@@ -28,7 +30,9 @@ export interface BaseMonitoringProps
2830
/**
2931
* An independent unit of monitoring. This is the base for all monitoring classes with alarm support.
3032
*/
31-
export abstract class Monitoring implements IDashboardSegment {
33+
export abstract class Monitoring
34+
implements IDashboardSegment, IDynamicDashboardSegment
35+
{
3236
protected readonly scope: MonitoringScope;
3337
protected readonly alarms: AlarmWithAnnotation[];
3438
protected readonly localAlarmNamePrefixOverride?: string;
@@ -101,4 +105,17 @@ export abstract class Monitoring implements IDashboardSegment {
101105
* Returns widgets to be placed on the main dashboard.
102106
*/
103107
abstract widgets(): IWidget[];
108+
109+
widgetsForDashboard(name: string): IWidget[] {
110+
switch (name) {
111+
case DefaultDashboards.SUMMARY:
112+
return this.summaryWidgets();
113+
case DefaultDashboards.DETAIL:
114+
return this.widgets();
115+
case DefaultDashboards.ALARMS:
116+
return this.alarmWidgets();
117+
default:
118+
return [];
119+
}
120+
}
104121
}

lib/dashboard/DefaultDashboardFactory.ts

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import {
55
PeriodOverride,
66
} from "aws-cdk-lib/aws-cloudwatch";
77
import { Construct } from "constructs";
8-
98
import { BitmapDashboard } from "./BitmapDashboard";
109
import { DashboardRenderingPreference } from "./DashboardRenderingPreference";
1110
import { DashboardWithBitmapCopy } from "./DashboardWithBitmapCopy";
11+
import { IDynamicDashboardSegment } from "./DynamicDashboardSegment";
1212
import { IDashboardFactory, IDashboardFactoryProps } from "./IDashboardFactory";
13+
import { IDynamicDashboardFactory } from "./IDynamicDashboardFactory";
1314

1415
export interface MonitoringDashboardsProps {
1516
/**
@@ -67,15 +68,25 @@ export interface MonitoringDashboardsProps {
6768
readonly renderingPreference?: DashboardRenderingPreference;
6869
}
6970

71+
export enum DefaultDashboards {
72+
SUMMARY = "Summary",
73+
DETAIL = "Detail",
74+
ALARMS = "Alarms",
75+
}
76+
7077
export class DefaultDashboardFactory
7178
extends Construct
72-
implements IDashboardFactory
79+
implements IDashboardFactory, IDynamicDashboardFactory
7380
{
81+
// Legacy Dashboard Fields
7482
readonly dashboard?: Dashboard;
7583
readonly summaryDashboard?: Dashboard;
7684
readonly alarmDashboard?: Dashboard;
7785
readonly anyDashboardCreated: boolean;
7886

87+
// Dynamic Dashboard Fields
88+
readonly dashboards: Record<string, Dashboard> = {};
89+
7990
constructor(scope: Construct, id: string, props: MonitoringDashboardsProps) {
8091
super(scope, id);
8192

@@ -108,6 +119,7 @@ export class DefaultDashboardFactory
108119
periodOverride:
109120
props.detailDashboardPeriodOverride ?? PeriodOverride.INHERIT,
110121
});
122+
this.dashboards[DefaultDashboards.DETAIL] = this.dashboard;
111123
}
112124
if (createSummaryDashboard) {
113125
anyDashboardCreated = true;
@@ -121,6 +133,7 @@ export class DefaultDashboardFactory
121133
props.summaryDashboardPeriodOverride ?? PeriodOverride.INHERIT,
122134
}
123135
);
136+
this.dashboards[DefaultDashboards.SUMMARY] = this.summaryDashboard;
124137
}
125138
if (createAlarmDashboard) {
126139
anyDashboardCreated = true;
@@ -134,26 +147,12 @@ export class DefaultDashboardFactory
134147
props.detailDashboardPeriodOverride ?? PeriodOverride.INHERIT,
135148
}
136149
);
150+
this.dashboards[DefaultDashboards.ALARMS] = this.alarmDashboard;
137151
}
138152

139153
this.anyDashboardCreated = anyDashboardCreated;
140154
}
141155

142-
protected createDashboard(
143-
renderingPreference: DashboardRenderingPreference,
144-
id: string,
145-
props: DashboardProps
146-
) {
147-
switch (renderingPreference) {
148-
case DashboardRenderingPreference.INTERACTIVE_ONLY:
149-
return new Dashboard(this, id, props);
150-
case DashboardRenderingPreference.BITMAP_ONLY:
151-
return new BitmapDashboard(this, id, props);
152-
case DashboardRenderingPreference.INTERACTIVE_AND_BITMAP:
153-
return new DashboardWithBitmapCopy(this, id, props);
154-
}
155-
}
156-
157156
addSegment(props: IDashboardFactoryProps) {
158157
if ((props.overrideProps?.addToDetailDashboard ?? true) && this.dashboard) {
159158
this.dashboard.addWidgets(...props.segment.widgets());
@@ -172,6 +171,33 @@ export class DefaultDashboardFactory
172171
}
173172
}
174173

174+
addDynamicSegment(segment: IDynamicDashboardSegment): void {
175+
this.dashboard?.addWidgets(
176+
...segment.widgetsForDashboard(DefaultDashboards.DETAIL)
177+
);
178+
this.summaryDashboard?.addWidgets(
179+
...segment.widgetsForDashboard(DefaultDashboards.SUMMARY)
180+
);
181+
this.alarmDashboard?.addWidgets(
182+
...segment.widgetsForDashboard(DefaultDashboards.ALARMS)
183+
);
184+
}
185+
186+
protected createDashboard(
187+
renderingPreference: DashboardRenderingPreference,
188+
id: string,
189+
props: DashboardProps
190+
) {
191+
switch (renderingPreference) {
192+
case DashboardRenderingPreference.INTERACTIVE_ONLY:
193+
return new Dashboard(this, id, props);
194+
case DashboardRenderingPreference.BITMAP_ONLY:
195+
return new BitmapDashboard(this, id, props);
196+
case DashboardRenderingPreference.INTERACTIVE_AND_BITMAP:
197+
return new DashboardWithBitmapCopy(this, id, props);
198+
}
199+
}
200+
175201
createdDashboard(): Dashboard | undefined {
176202
return this.dashboard;
177203
}
@@ -183,4 +209,17 @@ export class DefaultDashboardFactory
183209
createdAlarmDashboard(): Dashboard | undefined {
184210
return this.alarmDashboard;
185211
}
212+
213+
getDashboard(name: string): Dashboard | undefined {
214+
switch (name) {
215+
case DefaultDashboards.SUMMARY:
216+
return this.summaryDashboard;
217+
case DefaultDashboards.DETAIL:
218+
return this.dashboard;
219+
case DefaultDashboards.ALARMS:
220+
return this.alarmDashboard;
221+
default:
222+
throw new Error("Unexpected dashboard name!");
223+
}
224+
}
186225
}

0 commit comments

Comments
 (0)