-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
feat(cloudwatch): dashboard variables #26285
Conversation
- Add dashboard variables implementation - Add tests - Add integ test - Add section in README on variables
Hello reviewer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @Stacy-D. Thanks for this PR -- it is a great start! Just a few nits here and there and sorry that I did not like the phrase allow to
:)
packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.dashboard-variables.ts
Outdated
Show resolved
Hide resolved
Co-authored-by: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com>
Co-authored-by: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com>
@kaizencc just a question, re the enums introduced in this PR, in previous PRs we sometimes got the feedback to prefer classes with static members to kinda future proof against introductions of new types e.g. https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-lambda/lib/architecture.ts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export class SearchDashboardVariable extends DashboardVariable { | ||
private readonly props: ISearchDashboardVariable; | ||
|
||
constructor(props: ISearchDashboardVariable) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the API docs, search
and populateFrom
are required "if inputType is select or radio and you are not specifying values."
Just want to confirm that SearchDashboardVariable
will still work with inputType input
right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I went to try a different combo of parameters manually, and what would essentially happen if input
inputType and we have a search variable, is that it will just be a normal input
. However, on saving one is faced with the nice error
The dashboard body is invalid, there are 2 validation errors: [ { "dataPath": "/variables/0/search", "message": "Should NOT be valid" }, { "dataPath": "/variables/0/populateFrom", "message": "Should NOT be valid" } ]
So, to me there are 2 things to do here
SearchDashboardVariable
should validate thatinputType
is notinput
ISearchDashboardVariable.populateFrom
should not be optional
I think ValueDashboardVariable
might need some similar validations as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lovely. Thanks for the investigation. That's what I was afraid of, but the docs don't really say it...
/** | ||
* Optional label for the selected item | ||
* | ||
* @default - value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does value
mean here? It's kind of an oversaturated word... if you mean the value you specify in the 'value' prop, then lets be specific :)
*/ | ||
readonly searchExpression: string; | ||
/** | ||
* Optional dimension name from the search |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs a @default
. Not sure why this wouldn't throw an error somewhere in our PR build. Will have to take a look later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@humanzz Looks awesome! 3 last minute comments. Otherwise I'm happy with this :).
|
||
// WHEN | ||
dashboard.addVariable(new SearchDashboardVariable({ | ||
defaultValue: '__FIRST', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that __FIRST
is a special value. Can we make that into a constant so that it can be referenced like:
defaultValue: `DefaultValue.FIRST`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so just a class with a static constant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT? Maybe its unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I made it take a bit more responsibility in d8cc14a, let me know what you think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it. Will make DefaultValue.FIRST
more discoverable, and I expect that it will be a popular default value for most use cases.
* | ||
* For example `{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"` | ||
*/ | ||
readonly searchExpression: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you given any thought to strongly typing this expression? How unique can this string be? The examples in documentation all reference a namespace, dimension, and metricName in some format. Maybe with stronger types we can build this expression out for the user and not have to have them worry about typos.
I'm thinking something like:
class searchExpression {
public static FROM_COMPONENTS(
namespace: string,
dimension: string,
metricName: string,
) {
return new searchExpression(`{${namespace},${dimension}} MetricName=\"${metricName\"`);
}
// users can still supply raw expressions if they want
public constructor(public readonly expression: string) {}
}
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I'd be a bit hesitant here, without further investigation, abt how metrics with multiple dimensions work - if they're supported or not.
If you think it's needed, I can give that a try and see if that's supported, in which case the search expression would need to allow for number of dimensions >= 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
multiple dimensions are supported... I'll work to create a class for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! I think if possible, a class would be a better customer experience than a raw string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I blame you for the more code - now it needs more reviewing 😆
I ended up refactoring how search values are modeled!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, with this new change, I'm now wondering if we really need a separate ValueDashboardVariable
and SearchDashboardVariable
. The difference can really be just about the values
prop. Something like Values.fromSearch
and Values.fromStatic
(or any better name).
Worth pursuing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went ahead and did that. I believe it looks better.
Let me know what you think.
I also believe docs/README/error messages might need a good look to ensure they make sense for users.
…nto dashboard_vars
…nto dashboard_vars
- make DashbordVariable a concrete class - introduce Values abstract class with static methods for creating search-based and static values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay! I think this is the last review. We're almost there. Thanks for bearing with me @humanzz
|
||
// WHEN | ||
dashboard.addVariable(new SearchDashboardVariable({ | ||
defaultValue: '__FIRST', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it. Will make DefaultValue.FIRST
more discoverable, and I expect that it will be a popular default value for most use cases.
* @param metricName the metric name to be used in the search expression | ||
* @param populateFrom the dimension name, that the search expression retrieves, whose values will be used to populate the values to choose from | ||
*/ | ||
public static fromSearchComponents(namespace: string, dimensions: string[], metricName: string, populateFrom: string): Values { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export interface SearchComponentProps {
readonly namespace: string,
readonly dimensions: string[],
readonly metricName: string,
readonly populateFrom: string,
}
export class Values {
public static fromSearchComponents(props: SearchComponentProps): Values {
}
}
label: 'Function', | ||
inputType: cw.VariableInputType.RADIO, | ||
value: 'originalFuncNameInDashboard', | ||
values: cw.Values.fromSearchComponents('AWS/Lambda', ['FunctionName'], 'Duration', 'FunctionName'), // equivalent to cw.Values.fromSearch('{AWS/Lambda,FunctionName} MetricName=\"Duration\"', 'FunctionName'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
values: cw.Values.fromSearchComponents('AWS/Lambda', ['FunctionName'], 'Duration', 'FunctionName'), // equivalent to cw.Values.fromSearch('{AWS/Lambda,FunctionName} MetricName=\"Duration\"', 'FunctionName'), | |
// equivalent to cw.Values.fromSearch('{AWS/Lambda,FunctionName} MetricName=\"Duration\"', 'FunctionName'), | |
values: cw.Values.fromSearchComponents('AWS/Lambda', ['FunctionName'], 'Duration', 'FunctionName'), |
throw new Error(`Variable with inputType (${options.inputType}) requires values to be set`); | ||
} | ||
if (options.inputType == VariableInputType.INPUT && options.values) { | ||
throw new Error('Unsupported inputType INPUT. Please choose either SELECT or RADIO'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw new Error('Unsupported inputType INPUT. Please choose either SELECT or RADIO'); | |
throw new Error('inputType INPUT cannot be combined with values. Please choose either SELECT or RADIO or remove \'values\' from the configuration.'); |
|
||
The following example generates a Lambda function variable, with a radio button for each function. Functions are discovered by a metric query search. | ||
The `values` with `cw.Values.fromSearchComponents` indicates that the values will be populated from `FunctionName` values retrieved from the search expression `{AWS/Lambda,FunctionName} MetricName=\"Duration\"`. | ||
The `defaultValue` with `cw.DefaultValue.FIRST` indicates that the default value will be the first value returned from the search. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The `defaultValue` with `cw.DefaultValue.FIRST` indicates that the default value will be the first value returned from the search. | |
The `defaultValue` with `cw.DefaultValue.FIRST` indicates that the default value will be the first value returned from the search. |
/** | ||
* Create a default value | ||
* @param value the value to be used as default | ||
*/ | ||
public static of(value: any) { | ||
return new DefaultValue(value); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love of
. Reason being that we're not creating a default value 'of' something. We're just referencing a value. Some options:
DefaultValue.string('us-east-1')
DefaultValue.value('us-east-1') // contributes to overkill of the word value though
I don't really care. I just dont think of
is right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I honestly can't think of a better option than of
- except maybe the overkill value
you suggested :)
string
doesn't align well with the fact that it's actually any
, with the documentation saying Type: String, Number, or Boolean, depending on the type value for this variable
I think I'll go with value
no worries at all. I think we're making good progress here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet @humanzz! How wonderful to get multiple rounds of review bashed out in a day :). Really appreciate the fast turnaround time!
Awesome @kaizencc! |
Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
This change add the support for dashboard variables in CDK https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_dashboard_variables.html. It allows to reduce the number of repeated CloudWatch dashboards by unifying them into one managed with variables. Closes aws#26200 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
This change add the support for dashboard variables in CDK https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_dashboard_variables.html. It allows to reduce the number of repeated CloudWatch dashboards by unifying them into one managed with variables.
Closes #26200
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license