diff --git a/awsx-classic/cloudwatch/widgets_graph.ts b/awsx-classic/cloudwatch/widgets_graph.ts index 600905fe1..0fc04f348 100644 --- a/awsx-classic/cloudwatch/widgets_graph.ts +++ b/awsx-classic/cloudwatch/widgets_graph.ts @@ -1,94 +1,118 @@ -// Copyright 2016-2018, Pulumi Corporation. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as pulumi from "@pulumi/pulumi"; -import * as wjson from "./widgets_json"; - -import { MetricWidget, MetricWidgetArgs } from "./widgets_simple"; - -// Contains all the classes for easily making graph widgets in a dashboard. - -export interface GraphMetricWidgetArgs extends MetricWidgetArgs { - /** - * Limits for the minimums and maximums of the y-axis. This applies to every metric being - * graphed, unless specific metrics override it. - */ - yAxis?: pulumi.Input; -} - -export interface YAxis { - /** Optional min and max settings for the left Y-axis. */ - left?: MinMax; - - /** Optional min and max settings for the right Y-axis. */ - right?: MinMax; -} - -export interface MinMax { - /** The minimum value for this Y-axis */ - min?: number; - /** The maximum value for this Y-axis */ - max?: number; -} - - -/** - * Base type for widets that display metrics as a graph (either a line or stacked graph). - * - * See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/graph_metrics.html for more - * details. - */ -export abstract class GraphMetricWidget extends MetricWidget { - constructor(private readonly graphArgs: GraphMetricWidgetArgs) { - super(graphArgs); - } - - protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "timeSeries"; - protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => this.graphArgs.yAxis; -} - -/** - * Displays a set of metrics as a line graph. - */ -export class LineGraphMetricWidget extends GraphMetricWidget { - constructor(args: GraphMetricWidgetArgs) { - super(args); - } - - protected computedStacked = () => false; -} - -/** - * Displays a set of metrics as a stacked area graph. - */ -export class StackedAreaGraphMetricWidget extends GraphMetricWidget { - constructor(args: GraphMetricWidgetArgs) { - super(args); - } - - protected computedStacked = () => true; -} - -/** - * Displays a set of metrics as a single number. - */ -export class SingleNumberMetricWidget extends MetricWidget { - constructor(args: MetricWidgetArgs) { - super(args); - } - - protected computedStacked = () => false; - protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "singleValue"; - protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => undefined; -} +// Copyright 2016-2018, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as pulumi from "@pulumi/pulumi"; +import * as wjson from "./widgets_json"; + +import { MetricWidget, MetricWidgetArgs } from "./widgets_simple"; + +type DeepRequired = { + [K in keyof T]: Required> +} +// Contains all the classes for easily making graph widgets in a dashboard. + +export interface GraphMetricWidgetArgs extends MetricWidgetArgs { + /** + * Limits for the minimums and maximums of the y-axis. This applies to every metric being + * graphed, unless specific metrics override it. + */ + yAxis?: pulumi.Input; +} + +export interface GaugeMetricWidgetArgs extends MetricWidgetArgs { + /** + * Limits for the minimums and maximums of the y-axis. Needed for Gauge Widget. + */ + yAxis: pulumi.Input>; +} + + +export interface YAxis { + /** Optional min and max settings for the left Y-axis. */ + left?: MinMax; + + /** Optional min and max settings for the right Y-axis. */ + right?: MinMax; +} + +export interface MinMax { + /** The minimum value for this Y-axis */ + min?: number; + /** The maximum value for this Y-axis */ + max?: number; +} + + +/** + * Base type for widets that display metrics as a graph (either a line or stacked graph). + * + * See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/graph_metrics.html for more + * details. + */ +export abstract class GraphMetricWidget extends MetricWidget { + constructor(private readonly graphArgs: GraphMetricWidgetArgs) { + super(graphArgs); + } + + protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "timeSeries"; + protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => this.graphArgs.yAxis; +} + +/** + * Displays a set of metrics as a line graph. + */ +export class LineGraphMetricWidget extends GraphMetricWidget { + constructor(args: GraphMetricWidgetArgs) { + super(args); + } + + protected computedStacked = () => false; +} + +/** + * Displays a set of metrics as a stacked area graph. + */ +export class StackedAreaGraphMetricWidget extends GraphMetricWidget { + constructor(args: GraphMetricWidgetArgs) { + super(args); + } + + protected computedStacked = () => true; +} + +/** + * Displays a set of metrics as a single number. + */ +export class SingleNumberMetricWidget extends MetricWidget { + constructor(args: MetricWidgetArgs) { + super(args); + } + + protected computedStacked = () => false; + protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "singleValue"; + protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => undefined; +} + +/** + * Displays a set of metrics as a gauge number. + */ +export class GaugeMetricWidget extends MetricWidget { + constructor(private readonly gaugeArgs: GaugeMetricWidgetArgs) { + super(gaugeArgs); + } + + protected computedStacked = () => false; + protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "gauge"; + protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => this.gaugeArgs.yAxis; +} diff --git a/awsx-classic/cloudwatch/widgets_json.ts b/awsx-classic/cloudwatch/widgets_json.ts index d77615dcc..dde0f12a4 100644 --- a/awsx-classic/cloudwatch/widgets_json.ts +++ b/awsx-classic/cloudwatch/widgets_json.ts @@ -58,7 +58,7 @@ export interface MetricWidgetPropertiesJson { period: pulumi.Input | undefined; region: pulumi.Input; stat: pulumi.Input; - view: pulumi.Input<"timeSeries" | "singleValue" | undefined>; + view: pulumi.Input<"timeSeries" | "singleValue" | "gauge" | undefined>; stacked: pulumi.Input; yAxis: pulumi.Input | undefined; } diff --git a/awsx-classic/cloudwatch/widgets_simple.ts b/awsx-classic/cloudwatch/widgets_simple.ts index 7e8265e67..0c6fcefe9 100644 --- a/awsx-classic/cloudwatch/widgets_simple.ts +++ b/awsx-classic/cloudwatch/widgets_simple.ts @@ -397,7 +397,7 @@ export class LogWidget extends SimpleWidget { protected computeType(): wjson.LogWidgetJson["type"] { return "log"; } - protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "timeSeries"; + protected computeView = (): wjson.LogWidgetPropertiesJson["view"] => "timeSeries"; protected computedStacked = () => false; protected computeProperties(region: pulumi.Output): wjson.LogWidgetJson["properties"] { diff --git a/awsx-classic/tests/cloudwatch/cloudwatch.spec.ts b/awsx-classic/tests/cloudwatch/cloudwatch.spec.ts index 08b16ddae..b961c35ff 100644 --- a/awsx-classic/tests/cloudwatch/cloudwatch.spec.ts +++ b/awsx-classic/tests/cloudwatch/cloudwatch.spec.ts @@ -1,1210 +1,1568 @@ -// Copyright 2016-2018, Pulumi Corporation. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as pulumi from "@pulumi/pulumi"; - -import * as assert from "assert"; -import * as semver from "semver"; - -import * as dashboard from "../../cloudwatch/dashboard"; -import { Metric } from "../../cloudwatch/metric"; -import { Widget } from "../../cloudwatch/widget"; -import { AlarmAnnotation, HorizontalAnnotation, VerticalAnnotation } from "../../cloudwatch/widgets_annotations"; -import { ColumnWidget, RowWidget } from "../../cloudwatch/widgets_flow"; -import { LineGraphMetricWidget, SingleNumberMetricWidget, StackedAreaGraphMetricWidget } from "../../cloudwatch/widgets_graph"; -import { ExpressionWidgetMetric, TextWidget } from "../../cloudwatch/widgets_simple"; - -async function bodyJson(...widgets: Widget[]) { - return await toJson({ widgets }); -} - -async function toJson(body: dashboard.DashboardArgs) { - const op = dashboard.getDashboardBody(body, pulumi.output("us-east-2")).apply(b => JSON.stringify(b, null, 4)); - return await (op).promise(); -} - -describe("dashboard", () => { - it("empty", async () => { - const json = await bodyJson(); - assert.equal(json, `{ - "widgets": [] -}`); - }); - - it("period override", async () => { - const json = await toJson({ periodOverride: "auto", widgets: [] }); - assert.equal(json, `{ - "periodOverride": "auto", - "widgets": [] -}`); - }); - - describe("annotations", () => { - describe("alarms", () => { - if (semver.gte(process.version, "10.0.0")) { - it("multiple alarms", async () => { - await assert.rejects(async () => { - await bodyJson(new SingleNumberMetricWidget({ - annotations: [new AlarmAnnotation("some_arn"), new AlarmAnnotation("some_arn")], - })); - }); - }); - } - - it("single", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - annotations: [new AlarmAnnotation("some_arn")], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "annotations": { - "alarms": [ - "some_arn" - ] - }, - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - }); - - describe("horizontal", () => { - if (semver.gte(process.version, "10.0.0")) { - it("fill and band", async () => { - await assert.rejects(async () => { - await bodyJson(new SingleNumberMetricWidget({ - annotations: [new HorizontalAnnotation({ - fill: "above", - aboveEdge: { value: 10 }, - belowEdge: { value: 5 }, - })], - })); - }); - }); - } - - it("single", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - annotations: [new HorizontalAnnotation({ - aboveEdge: { value: 10 }, - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "annotations": { - "horizontal": [ - { - "value": 10 - } - ] - }, - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - it("band", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - annotations: [new HorizontalAnnotation({ - aboveEdge: { value: 10 }, - belowEdge: { value: 5 }, - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "annotations": { - "horizontal": [ - { - "value": 10 - }, - { - "value": 5 - } - ] - }, - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - }); - - describe("vertical", () => { - if (semver.gte(process.version, "10.0.0")) { - it("fill and band", async () => { - await assert.rejects(async () => { - await bodyJson(new SingleNumberMetricWidget({ - annotations: [new VerticalAnnotation({ - fill: "after", - beforeEdge: { value: "10" }, - afterEdge: { value: "5" }, - })], - })); - }); - }); - } - - it("single", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - annotations: [new VerticalAnnotation({ - beforeEdge: { value: "10" }, - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "annotations": { - "vertical": [ - { - "value": "10" - } - ] - }, - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - it("band", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - annotations: [new VerticalAnnotation({ - beforeEdge: { value: "10" }, - afterEdge: { value: "5" }, - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "annotations": { - "vertical": [ - { - "value": "10" - }, - { - "value": "5" - } - ] - }, - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - }); - }); - - describe("text widgets", () => { - it("string constructor", async () => { - const json = await bodyJson(new TextWidget("text")); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "text" - } - } - ] -}`); - }); - - it("custom constructor", async () => { - const json = await bodyJson(new TextWidget({ markdown: "text", width: 2, height: 1 })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 2, - "height": 1, - "type": "text", - "properties": { - "markdown": "text" - } - } - ] -}`); - }); - }); - - describe("flow widgets", () => { - describe("horizontal", () => { - it("two widgets", async () => { - const json = await bodyJson(new TextWidget("hello"), new TextWidget("world")); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "hello" - } - }, - { - "x": 6, - "y": 0, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "world" - } - } - ] -}`); - }); - it("multiple widgets with wrapping", async () => { - const json = await bodyJson( - new TextWidget("hello"), - new TextWidget("world"), - new TextWidget({ markdown: "goodnight", height: 1, width: 18 }), - new TextWidget({ markdown: "moon", height: 3, width: 6 }), - new TextWidget({ markdown: "!", height: 5, width: 1 })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "hello" - } - }, - { - "x": 6, - "y": 0, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "world" - } - }, - { - "x": 0, - "y": 6, - "width": 18, - "height": 1, - "type": "text", - "properties": { - "markdown": "goodnight" - } - }, - { - "x": 18, - "y": 6, - "width": 6, - "height": 3, - "type": "text", - "properties": { - "markdown": "moon" - } - }, - { - "x": 0, - "y": 9, - "width": 1, - "height": 5, - "type": "text", - "properties": { - "markdown": "!" - } - } - ] -}`); - }); - it("multiple rows with wrapping", async () => { - const json = await bodyJson( - new RowWidget( - new TextWidget({ markdown: "hello", height: 4 }), - new TextWidget({ markdown: "world", height: 3 }), - new RowWidget( - new TextWidget({ markdown: "goodnight", height: 1, width: 10 }), - new TextWidget({ markdown: "moon", height: 3, width: 10 }), - new TextWidget({ markdown: "!", height: 5, width: 10 })), - new RowWidget(new TextWidget("byebye")))); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 4, - "type": "text", - "properties": { - "markdown": "hello" - } - }, - { - "x": 6, - "y": 0, - "width": 6, - "height": 3, - "type": "text", - "properties": { - "markdown": "world" - } - }, - { - "x": 0, - "y": 4, - "width": 10, - "height": 1, - "type": "text", - "properties": { - "markdown": "goodnight" - } - }, - { - "x": 10, - "y": 4, - "width": 10, - "height": 3, - "type": "text", - "properties": { - "markdown": "moon" - } - }, - { - "x": 0, - "y": 7, - "width": 10, - "height": 5, - "type": "text", - "properties": { - "markdown": "!" - } - }, - { - "x": 0, - "y": 12, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "byebye" - } - } - ] -}`); - }); - }); - - describe("vertical", () => { - it("two widgets", async () => { - const json = await bodyJson( - new ColumnWidget(new TextWidget("hello"), new TextWidget("world"))); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "hello" - } - }, - { - "x": 0, - "y": 6, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "world" - } - } - ] -}`); - }); - it("multiple widgets without wrapping", async () => { - const json = await bodyJson(new ColumnWidget( - new TextWidget("hello"), - new TextWidget("world"), - new TextWidget({ markdown: "goodnight", height: 1, width: 18 }), - new TextWidget({ markdown: "moon", height: 3, width: 6 }), - new TextWidget({ markdown: "!", height: 5, width: 1 }))); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "hello" - } - }, - { - "x": 0, - "y": 6, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "world" - } - }, - { - "x": 0, - "y": 12, - "width": 18, - "height": 1, - "type": "text", - "properties": { - "markdown": "goodnight" - } - }, - { - "x": 0, - "y": 13, - "width": 6, - "height": 3, - "type": "text", - "properties": { - "markdown": "moon" - } - }, - { - "x": 0, - "y": 16, - "width": 1, - "height": 5, - "type": "text", - "properties": { - "markdown": "!" - } - } - ] -}`); - }); - it("multiple columns without wrapping", async () => { - const json = await bodyJson( - new ColumnWidget( - new TextWidget({ markdown: "hello", height: 4 }), - new TextWidget({ markdown: "world", height: 3 })), - new ColumnWidget( - new TextWidget({ markdown: "goodnight", height: 1, width: 10 }), - new TextWidget({ markdown: "moon", height: 3, width: 10 }), - new TextWidget({ markdown: "!", height: 5, width: 10 })), - new ColumnWidget(new TextWidget("byebye")), - new ColumnWidget(new TextWidget("byebye2"))); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 4, - "type": "text", - "properties": { - "markdown": "hello" - } - }, - { - "x": 0, - "y": 4, - "width": 6, - "height": 3, - "type": "text", - "properties": { - "markdown": "world" - } - }, - { - "x": 6, - "y": 0, - "width": 10, - "height": 1, - "type": "text", - "properties": { - "markdown": "goodnight" - } - }, - { - "x": 6, - "y": 1, - "width": 10, - "height": 3, - "type": "text", - "properties": { - "markdown": "moon" - } - }, - { - "x": 6, - "y": 4, - "width": 10, - "height": 5, - "type": "text", - "properties": { - "markdown": "!" - } - }, - { - "x": 16, - "y": 0, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "byebye" - } - }, - { - "x": 0, - "y": 9, - "width": 6, - "height": 6, - "type": "text", - "properties": { - "markdown": "byebye2" - } - } - ] -}`); - }); - }); - }); - - describe("metric widgets", () => { - describe("single number", () => { - if (semver.gte(process.version, "10.0.0")) { - it("empty metrics", async () => { - await assert.rejects(async () => { - const json = await bodyJson(new SingleNumberMetricWidget({})); - }); - }); - - it("invalid period", async () => { - await assert.rejects(async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - metrics: [new Metric({ namespace: "AWS/EC2", name: "NetworkIn", period: 5 })], - })); - }); - }); - } - - it("single metric", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - metrics: [new Metric({ - namespace: "AWS/Lambda", - name: "Invocations", - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "metrics": [ - [ - "AWS/Lambda", - "Invocations", - { - "stat": "Average", - "period": 300, - "visible": true, - "yAxis": "left" - } - ] - ], - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - - it("stat", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - metrics: [new Metric({ - namespace: "AWS/Lambda", - name: "Invocations", - statistic: "SampleCount", - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "metrics": [ - [ - "AWS/Lambda", - "Invocations", - { - "stat": "SampleCount", - "period": 300, - "visible": true, - "yAxis": "left" - } - ] - ], - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - - it("extended stat", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - metrics: [new Metric({ - namespace: "AWS/Lambda", - name: "Invocations", - extendedStatistic: 99, - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "metrics": [ - [ - "AWS/Lambda", - "Invocations", - { - "stat": "p99", - "period": 300, - "visible": true, - "yAxis": "left" - } - ] - ], - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - - it("multiple metrics", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - metrics: [ - new Metric({ namespace: "AWS/Lambda", name: "Invocations", yAxis: "right" }), - new Metric({ namespace: "AWS/EC2", name: "NetworkIn", period: 60 }), - ], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "metrics": [ - [ - "AWS/Lambda", - "Invocations", - { - "stat": "Average", - "period": 300, - "visible": true, - "yAxis": "right" - } - ], - [ - "AWS/EC2", - "NetworkIn", - { - "stat": "Average", - "period": 60, - "visible": true, - "yAxis": "left" - } - ] - ], - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - - it("with dimension", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - metrics: [new Metric({ - namespace: "AWS/Lambda", - name: "Invocations", - dimensions: { - FunctionName: "MyFunc", - Hello: "world", - }, - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "metrics": [ - [ - "AWS/Lambda", - "Invocations", - "FunctionName", - "MyFunc", - "Hello", - "world", - { - "stat": "Average", - "period": 300, - "visible": true, - "yAxis": "left" - } - ] - ], - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - - it("alarm annotation", async () => { - const json = await bodyJson(new SingleNumberMetricWidget({ - metrics: [new Metric({ - namespace: "AWS/Lambda", - name: "Invocations", - })], - annotations: [new AlarmAnnotation("some_arn")], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "metrics": [ - [ - "AWS/Lambda", - "Invocations", - { - "stat": "Average", - "period": 300, - "visible": true, - "yAxis": "left" - } - ] - ], - "annotations": { - "alarms": [ - "some_arn" - ] - }, - "period": 300, - "region": "us-east-2", - "view": "singleValue", - "stacked": false - } - } - ] -}`); - }); - }); - - describe("stacked area graph", () => { - it("single metric", async () => { - const json = await bodyJson(new StackedAreaGraphMetricWidget({ - metrics: [new Metric({ - namespace: "AWS/Lambda", - name: "Invocations", - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "metrics": [ - [ - "AWS/Lambda", - "Invocations", - { - "stat": "Average", - "period": 300, - "visible": true, - "yAxis": "left" - } - ] - ], - "period": 300, - "region": "us-east-2", - "view": "timeSeries", - "stacked": true - } - } - ] -}`); - }); - }); - - describe("line graph", () => { - it("single metric", async () => { - const json = await bodyJson(new LineGraphMetricWidget({ - metrics: [new Metric({ - namespace: "AWS/Lambda", - name: "Invocations", - })], - })); - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "type": "metric", - "properties": { - "metrics": [ - [ - "AWS/Lambda", - "Invocations", - { - "stat": "Average", - "period": 300, - "visible": true, - "yAxis": "left" - } - ] - ], - "period": 300, - "region": "us-east-2", - "view": "timeSeries", - "stacked": false - } - } - ] -}`); - }); - }); - }); - - describe("realworld", () => { - it("realword 1", async () => { - const json = await toJson({ - start: "-PT6H", - periodOverride: "inherit", - widgets: [ - new LineGraphMetricWidget({ - width: 12, height: 6, - period: 300, - statistic: "Average", - title: "EC2 Instance CPU", - metrics: [ - new Metric({ - namespace: "AWS/EC2", - name: "DiskReadBytes", - dimensions: { - InstanceId: "i-123", - }, - }), - new ExpressionWidgetMetric("SUM(METRICS())", "Sum of DiskReadbytes", "e3"), - ], - }), - new LineGraphMetricWidget({ - width: 18, height: 9, - period: 300, - statistic: "Average", - title: "EC2 Instance CPU", - metrics: [ - new ExpressionWidgetMetric("SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", undefined, "e1"), - ], - }), - ], - }); - - assert.equal(json, `{ - "start": "-PT6H", - "periodOverride": "inherit", - "widgets": [ - { - "x": 0, - "y": 0, - "width": 12, - "height": 6, - "type": "metric", - "properties": { - "stat": "Average", - "metrics": [ - [ - "AWS/EC2", - "DiskReadBytes", - "InstanceId", - "i-123", - { - "stat": "Average", - "period": 300, - "visible": true, - "yAxis": "left" - } - ], - [ - { - "expression": "SUM(METRICS())", - "label": "Sum of DiskReadbytes", - "id": "e3" - } - ] - ], - "title": "EC2 Instance CPU", - "period": 300, - "region": "us-east-2", - "view": "timeSeries", - "stacked": false - } - }, - { - "x": 0, - "y": 6, - "width": 18, - "height": 9, - "type": "metric", - "properties": { - "stat": "Average", - "metrics": [ - [ - { - "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\\"CPUUtilization\\"', 'Average', 300)", - "id": "e1" - } - ] - ], - "title": "EC2 Instance CPU", - "period": 300, - "region": "us-east-2", - "view": "timeSeries", - "stacked": false - } - } - ] -}`); - }); - - it("realword 2", async () => { - const json = await bodyJson(new StackedAreaGraphMetricWidget({ - width: 12, height: 6, - period: 300, - statistic: "Average", - title: "EC2 Instance CPU", - yAxis: { left: { min: 0, max: 100 }, right: { min: 50 } }, - annotations: [new HorizontalAnnotation({ - aboveEdge: { - "label": "Critical range", - "value": 20, - }, - "visible": true, - "color": "#9467bd", - "fill": "above", - "yAxis": "right", - })], - metrics: [ - new Metric({ - namespace: "AWS/EC2", - name: "CPUUtilization", - dimensions: { - InstanceId: "i-012345", - }, - }), - new Metric({ - namespace: "AWS/EC2", - name: "NetworkIn", - dimensions: { - InstanceId: "i-012345", - }, - yAxis: "right", - label: "NetworkIn", - period: 3600, - statistic: "Maximum", - }), - ], - })); - - assert.equal(json, `{ - "widgets": [ - { - "x": 0, - "y": 0, - "width": 12, - "height": 6, - "type": "metric", - "properties": { - "stat": "Average", - "metrics": [ - [ - "AWS/EC2", - "CPUUtilization", - "InstanceId", - "i-012345", - { - "stat": "Average", - "period": 300, - "visible": true, - "yAxis": "left" - } - ], - [ - "AWS/EC2", - "NetworkIn", - "InstanceId", - "i-012345", - { - "stat": "Maximum", - "label": "NetworkIn", - "period": 3600, - "visible": true, - "yAxis": "right" - } - ] - ], - "annotations": { - "horizontal": [ - { - "fill": "above", - "color": "#9467bd", - "label": "Critical range", - "value": 20, - "visible": true, - "yAxis": "right" - } - ] - }, - "title": "EC2 Instance CPU", - "period": 300, - "region": "us-east-2", - "view": "timeSeries", - "stacked": true, - "yAxis": { - "left": { - "min": 0, - "max": 100 - }, - "right": { - "min": 50 - } - } - } - } - ] -}`); - }); - }); -}); +// Copyright 2016-2018, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as pulumi from "@pulumi/pulumi"; + +import * as assert from "assert"; +import * as semver from "semver"; + +import * as dashboard from "../../cloudwatch/dashboard"; +import { Metric } from "../../cloudwatch/metric"; +import { Widget } from "../../cloudwatch/widget"; +import { AlarmAnnotation, HorizontalAnnotation, VerticalAnnotation } from "../../cloudwatch/widgets_annotations"; +import { ColumnWidget, RowWidget } from "../../cloudwatch/widgets_flow"; +import { GaugeMetricWidget, LineGraphMetricWidget, SingleNumberMetricWidget, StackedAreaGraphMetricWidget } from "../../cloudwatch/widgets_graph"; +import { ExpressionWidgetMetric, TextWidget } from "../../cloudwatch/widgets_simple"; + +async function bodyJson(...widgets: Widget[]) { + return await toJson({ widgets }); +} + +async function toJson(body: dashboard.DashboardArgs) { + const op = dashboard.getDashboardBody(body, pulumi.output("us-east-2")).apply(b => JSON.stringify(b, null, 4)); + return await (op).promise(); +} + +describe("dashboard", () => { + it("empty", async () => { + const json = await bodyJson(); + assert.equal(json, `{ + "widgets": [] +}`); + }); + + it("period override", async () => { + const json = await toJson({ periodOverride: "auto", widgets: [] }); + assert.equal(json, `{ + "periodOverride": "auto", + "widgets": [] +}`); + }); + + describe("annotations", () => { + describe("alarms", () => { + if (semver.gte(process.version, "10.0.0")) { + it("multiple alarms", async () => { + await assert.rejects(async () => { + await bodyJson(new SingleNumberMetricWidget({ + annotations: [new AlarmAnnotation("some_arn"), new AlarmAnnotation("some_arn")], + })); + }); + }); + } + + it("single", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + annotations: [new AlarmAnnotation("some_arn")], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "annotations": { + "alarms": [ + "some_arn" + ] + }, + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + }); + + describe("horizontal", () => { + if (semver.gte(process.version, "10.0.0")) { + it("fill and band", async () => { + await assert.rejects(async () => { + await bodyJson(new SingleNumberMetricWidget({ + annotations: [new HorizontalAnnotation({ + fill: "above", + aboveEdge: { value: 10 }, + belowEdge: { value: 5 }, + })], + })); + }); + }); + } + + it("single", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + annotations: [new HorizontalAnnotation({ + aboveEdge: { value: 10 }, + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "annotations": { + "horizontal": [ + { + "value": 10 + } + ] + }, + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + it("band", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + annotations: [new HorizontalAnnotation({ + aboveEdge: { value: 10 }, + belowEdge: { value: 5 }, + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "annotations": { + "horizontal": [ + { + "value": 10 + }, + { + "value": 5 + } + ] + }, + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + }); + + describe("vertical", () => { + if (semver.gte(process.version, "10.0.0")) { + it("fill and band", async () => { + await assert.rejects(async () => { + await bodyJson(new SingleNumberMetricWidget({ + annotations: [new VerticalAnnotation({ + fill: "after", + beforeEdge: { value: "10" }, + afterEdge: { value: "5" }, + })], + })); + }); + }); + } + + it("single", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + annotations: [new VerticalAnnotation({ + beforeEdge: { value: "10" }, + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "annotations": { + "vertical": [ + { + "value": "10" + } + ] + }, + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + it("band", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + annotations: [new VerticalAnnotation({ + beforeEdge: { value: "10" }, + afterEdge: { value: "5" }, + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "annotations": { + "vertical": [ + { + "value": "10" + }, + { + "value": "5" + } + ] + }, + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + }); + }); + + describe("text widgets", () => { + it("string constructor", async () => { + const json = await bodyJson(new TextWidget("text")); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "text" + } + } + ] +}`); + }); + + it("custom constructor", async () => { + const json = await bodyJson(new TextWidget({ markdown: "text", width: 2, height: 1 })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 2, + "height": 1, + "type": "text", + "properties": { + "markdown": "text" + } + } + ] +}`); + }); + }); + + describe("flow widgets", () => { + describe("horizontal", () => { + it("two widgets", async () => { + const json = await bodyJson(new TextWidget("hello"), new TextWidget("world")); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "hello" + } + }, + { + "x": 6, + "y": 0, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "world" + } + } + ] +}`); + }); + it("multiple widgets with wrapping", async () => { + const json = await bodyJson( + new TextWidget("hello"), + new TextWidget("world"), + new TextWidget({ markdown: "goodnight", height: 1, width: 18 }), + new TextWidget({ markdown: "moon", height: 3, width: 6 }), + new TextWidget({ markdown: "!", height: 5, width: 1 })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "hello" + } + }, + { + "x": 6, + "y": 0, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "world" + } + }, + { + "x": 0, + "y": 6, + "width": 18, + "height": 1, + "type": "text", + "properties": { + "markdown": "goodnight" + } + }, + { + "x": 18, + "y": 6, + "width": 6, + "height": 3, + "type": "text", + "properties": { + "markdown": "moon" + } + }, + { + "x": 0, + "y": 9, + "width": 1, + "height": 5, + "type": "text", + "properties": { + "markdown": "!" + } + } + ] +}`); + }); + it("multiple rows with wrapping", async () => { + const json = await bodyJson( + new RowWidget( + new TextWidget({ markdown: "hello", height: 4 }), + new TextWidget({ markdown: "world", height: 3 }), + new RowWidget( + new TextWidget({ markdown: "goodnight", height: 1, width: 10 }), + new TextWidget({ markdown: "moon", height: 3, width: 10 }), + new TextWidget({ markdown: "!", height: 5, width: 10 })), + new RowWidget(new TextWidget("byebye")))); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 4, + "type": "text", + "properties": { + "markdown": "hello" + } + }, + { + "x": 6, + "y": 0, + "width": 6, + "height": 3, + "type": "text", + "properties": { + "markdown": "world" + } + }, + { + "x": 0, + "y": 4, + "width": 10, + "height": 1, + "type": "text", + "properties": { + "markdown": "goodnight" + } + }, + { + "x": 10, + "y": 4, + "width": 10, + "height": 3, + "type": "text", + "properties": { + "markdown": "moon" + } + }, + { + "x": 0, + "y": 7, + "width": 10, + "height": 5, + "type": "text", + "properties": { + "markdown": "!" + } + }, + { + "x": 0, + "y": 12, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "byebye" + } + } + ] +}`); + }); + }); + + describe("vertical", () => { + it("two widgets", async () => { + const json = await bodyJson( + new ColumnWidget(new TextWidget("hello"), new TextWidget("world"))); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "hello" + } + }, + { + "x": 0, + "y": 6, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "world" + } + } + ] +}`); + }); + it("multiple widgets without wrapping", async () => { + const json = await bodyJson(new ColumnWidget( + new TextWidget("hello"), + new TextWidget("world"), + new TextWidget({ markdown: "goodnight", height: 1, width: 18 }), + new TextWidget({ markdown: "moon", height: 3, width: 6 }), + new TextWidget({ markdown: "!", height: 5, width: 1 }))); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "hello" + } + }, + { + "x": 0, + "y": 6, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "world" + } + }, + { + "x": 0, + "y": 12, + "width": 18, + "height": 1, + "type": "text", + "properties": { + "markdown": "goodnight" + } + }, + { + "x": 0, + "y": 13, + "width": 6, + "height": 3, + "type": "text", + "properties": { + "markdown": "moon" + } + }, + { + "x": 0, + "y": 16, + "width": 1, + "height": 5, + "type": "text", + "properties": { + "markdown": "!" + } + } + ] +}`); + }); + it("multiple columns without wrapping", async () => { + const json = await bodyJson( + new ColumnWidget( + new TextWidget({ markdown: "hello", height: 4 }), + new TextWidget({ markdown: "world", height: 3 })), + new ColumnWidget( + new TextWidget({ markdown: "goodnight", height: 1, width: 10 }), + new TextWidget({ markdown: "moon", height: 3, width: 10 }), + new TextWidget({ markdown: "!", height: 5, width: 10 })), + new ColumnWidget(new TextWidget("byebye")), + new ColumnWidget(new TextWidget("byebye2"))); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 4, + "type": "text", + "properties": { + "markdown": "hello" + } + }, + { + "x": 0, + "y": 4, + "width": 6, + "height": 3, + "type": "text", + "properties": { + "markdown": "world" + } + }, + { + "x": 6, + "y": 0, + "width": 10, + "height": 1, + "type": "text", + "properties": { + "markdown": "goodnight" + } + }, + { + "x": 6, + "y": 1, + "width": 10, + "height": 3, + "type": "text", + "properties": { + "markdown": "moon" + } + }, + { + "x": 6, + "y": 4, + "width": 10, + "height": 5, + "type": "text", + "properties": { + "markdown": "!" + } + }, + { + "x": 16, + "y": 0, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "byebye" + } + }, + { + "x": 0, + "y": 9, + "width": 6, + "height": 6, + "type": "text", + "properties": { + "markdown": "byebye2" + } + } + ] +}`); + }); + }); + }); + + describe("metric widgets", () => { + describe("single number", () => { + if (semver.gte(process.version, "10.0.0")) { + it("empty metrics", async () => { + await assert.rejects(async () => { + const json = await bodyJson(new SingleNumberMetricWidget({})); + }); + }); + + it("invalid period", async () => { + await assert.rejects(async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + metrics: [new Metric({ namespace: "AWS/EC2", name: "NetworkIn", period: 5 })], + })); + }); + }); + } + + it("single metric", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + + it("stat", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + statistic: "SampleCount", + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "SampleCount", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + + it("extended stat", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + extendedStatistic: 99, + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "p99", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + + it("multiple metrics", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + metrics: [ + new Metric({ namespace: "AWS/Lambda", name: "Invocations", yAxis: "right" }), + new Metric({ namespace: "AWS/EC2", name: "NetworkIn", period: 60 }), + ], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "right" + } + ], + [ + "AWS/EC2", + "NetworkIn", + { + "stat": "Average", + "period": 60, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + + it("with dimension", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + dimensions: { + FunctionName: "MyFunc", + Hello: "world", + }, + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + "FunctionName", + "MyFunc", + "Hello", + "world", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + + it("alarm annotation", async () => { + const json = await bodyJson(new SingleNumberMetricWidget({ + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + })], + annotations: [new AlarmAnnotation("some_arn")], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "annotations": { + "alarms": [ + "some_arn" + ] + }, + "period": 300, + "region": "us-east-2", + "view": "singleValue", + "stacked": false + } + } + ] +}`); + }); + }); + + describe("stacked area graph", () => { + it("single metric", async () => { + const json = await bodyJson(new StackedAreaGraphMetricWidget({ + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "timeSeries", + "stacked": true + } + } + ] +}`); + }); + }); + + describe("line graph", () => { + it("single metric", async () => { + const json = await bodyJson(new LineGraphMetricWidget({ + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "timeSeries", + "stacked": false + } + } + ] +}`); + }); + }); + + describe("gauge number", () => { + if (semver.gte(process.version, "10.0.0")) { + it("empty metrics", async () => { + await assert.rejects(async () => { + const json = await bodyJson(new GaugeMetricWidget({ + yAxis: { + left: { + min: 0, + max: 100, + } + }, + })); + }); + }); + + it("invalid period", async () => { + await assert.rejects(async () => { + const json = await bodyJson(new GaugeMetricWidget({ + yAxis: { + left: { + min: 0, + max: 100, + } + }, + metrics: [new Metric({ namespace: "AWS/EC2", name: "NetworkIn", period: 5 })], + })); + }); + }); + } + + it("single metric", async () => { + const json = await bodyJson(new GaugeMetricWidget({ + yAxis: { + left: { + min: 0, + max: 100, + } + }, + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "gauge", + "stacked": false, + "yAxis": { + "left": { + "min": 0, + "max": 100 + } + } + } + } + ] +}`); + }); + + it("stat", async () => { + const json = await bodyJson(new GaugeMetricWidget({ + yAxis: { + left: { + min: 0, + max: 100, + } + }, + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + statistic: "SampleCount", + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "SampleCount", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "gauge", + "stacked": false, + "yAxis": { + "left": { + "min": 0, + "max": 100 + } + } + } + } + ] +}`); + }); + + it("extended stat", async () => { + const json = await bodyJson(new GaugeMetricWidget({ + yAxis: { + left: { + min: 0, + max: 100, + } + }, + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + extendedStatistic: 99, + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "p99", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "gauge", + "stacked": false, + "yAxis": { + "left": { + "min": 0, + "max": 100 + } + } + } + } + ] +}`); + }); + + it("multiple metrics", async () => { + const json = await bodyJson(new GaugeMetricWidget({ + yAxis: { + left: { + min: 0, + max: 100, + } + }, + metrics: [ + new Metric({ namespace: "AWS/Lambda", name: "Invocations", yAxis: "right" }), + new Metric({ namespace: "AWS/EC2", name: "NetworkIn", period: 60 }), + ], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "right" + } + ], + [ + "AWS/EC2", + "NetworkIn", + { + "stat": "Average", + "period": 60, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "gauge", + "stacked": false, + "yAxis": { + "left": { + "min": 0, + "max": 100 + } + } + } + } + ] +}`); + }); + + it("with dimension", async () => { + const json = await bodyJson(new GaugeMetricWidget({ + yAxis: { + left: { + min: 0, + max: 100, + } + }, + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + dimensions: { + FunctionName: "MyFunc", + Hello: "world", + }, + })], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + "FunctionName", + "MyFunc", + "Hello", + "world", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "period": 300, + "region": "us-east-2", + "view": "gauge", + "stacked": false, + "yAxis": { + "left": { + "min": 0, + "max": 100 + } + } + } + } + ] +}`); + }); + + it("alarm annotation", async () => { + const json = await bodyJson(new GaugeMetricWidget({ + yAxis: { + left: { + min: 0, + max: 100, + } + }, + metrics: [new Metric({ + namespace: "AWS/Lambda", + name: "Invocations", + })], + annotations: [new AlarmAnnotation("some_arn")], + })); + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "type": "metric", + "properties": { + "metrics": [ + [ + "AWS/Lambda", + "Invocations", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ] + ], + "annotations": { + "alarms": [ + "some_arn" + ] + }, + "period": 300, + "region": "us-east-2", + "view": "gauge", + "stacked": false, + "yAxis": { + "left": { + "min": 0, + "max": 100 + } + } + } + } + ] +}`); + }); + }); + }); + }); + + describe("realworld", () => { + it("realword 1", async () => { + const json = await toJson({ + start: "-PT6H", + periodOverride: "inherit", + widgets: [ + new LineGraphMetricWidget({ + width: 12, height: 6, + period: 300, + statistic: "Average", + title: "EC2 Instance CPU", + metrics: [ + new Metric({ + namespace: "AWS/EC2", + name: "DiskReadBytes", + dimensions: { + InstanceId: "i-123", + }, + }), + new ExpressionWidgetMetric("SUM(METRICS())", "Sum of DiskReadbytes", "e3"), + ], + }), + new LineGraphMetricWidget({ + width: 18, height: 9, + period: 300, + statistic: "Average", + title: "EC2 Instance CPU", + metrics: [ + new ExpressionWidgetMetric("SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", undefined, "e1"), + ], + }), + ], + }); + + assert.equal(json, `{ + "start": "-PT6H", + "periodOverride": "inherit", + "widgets": [ + { + "x": 0, + "y": 0, + "width": 12, + "height": 6, + "type": "metric", + "properties": { + "stat": "Average", + "metrics": [ + [ + "AWS/EC2", + "DiskReadBytes", + "InstanceId", + "i-123", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ], + [ + { + "expression": "SUM(METRICS())", + "label": "Sum of DiskReadbytes", + "id": "e3" + } + ] + ], + "title": "EC2 Instance CPU", + "period": 300, + "region": "us-east-2", + "view": "timeSeries", + "stacked": false + } + }, + { + "x": 0, + "y": 6, + "width": 18, + "height": 9, + "type": "metric", + "properties": { + "stat": "Average", + "metrics": [ + [ + { + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\\"CPUUtilization\\"', 'Average', 300)", + "id": "e1" + } + ] + ], + "title": "EC2 Instance CPU", + "period": 300, + "region": "us-east-2", + "view": "timeSeries", + "stacked": false + } + } + ] +}`); + }); + + it("realword 2", async () => { + const json = await bodyJson(new StackedAreaGraphMetricWidget({ + width: 12, height: 6, + period: 300, + statistic: "Average", + title: "EC2 Instance CPU", + yAxis: { left: { min: 0, max: 100 }, right: { min: 50 } }, + annotations: [new HorizontalAnnotation({ + aboveEdge: { + "label": "Critical range", + "value": 20, + }, + "visible": true, + "color": "#9467bd", + "fill": "above", + "yAxis": "right", + })], + metrics: [ + new Metric({ + namespace: "AWS/EC2", + name: "CPUUtilization", + dimensions: { + InstanceId: "i-012345", + }, + }), + new Metric({ + namespace: "AWS/EC2", + name: "NetworkIn", + dimensions: { + InstanceId: "i-012345", + }, + yAxis: "right", + label: "NetworkIn", + period: 3600, + statistic: "Maximum", + }), + ], + })); + + assert.equal(json, `{ + "widgets": [ + { + "x": 0, + "y": 0, + "width": 12, + "height": 6, + "type": "metric", + "properties": { + "stat": "Average", + "metrics": [ + [ + "AWS/EC2", + "CPUUtilization", + "InstanceId", + "i-012345", + { + "stat": "Average", + "period": 300, + "visible": true, + "yAxis": "left" + } + ], + [ + "AWS/EC2", + "NetworkIn", + "InstanceId", + "i-012345", + { + "stat": "Maximum", + "label": "NetworkIn", + "period": 3600, + "visible": true, + "yAxis": "right" + } + ] + ], + "annotations": { + "horizontal": [ + { + "fill": "above", + "color": "#9467bd", + "label": "Critical range", + "value": 20, + "visible": true, + "yAxis": "right" + } + ] + }, + "title": "EC2 Instance CPU", + "period": 300, + "region": "us-east-2", + "view": "timeSeries", + "stacked": true, + "yAxis": { + "left": { + "min": 0, + "max": 100 + }, + "right": { + "min": 50 + } + } + } + } + ] +}`); + }); + }); +}); diff --git a/sdk/nodejs/classic/cloudwatch/widgets_graph.ts b/sdk/nodejs/classic/cloudwatch/widgets_graph.ts index 600905fe1..0fc04f348 100644 --- a/sdk/nodejs/classic/cloudwatch/widgets_graph.ts +++ b/sdk/nodejs/classic/cloudwatch/widgets_graph.ts @@ -1,94 +1,118 @@ -// Copyright 2016-2018, Pulumi Corporation. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as pulumi from "@pulumi/pulumi"; -import * as wjson from "./widgets_json"; - -import { MetricWidget, MetricWidgetArgs } from "./widgets_simple"; - -// Contains all the classes for easily making graph widgets in a dashboard. - -export interface GraphMetricWidgetArgs extends MetricWidgetArgs { - /** - * Limits for the minimums and maximums of the y-axis. This applies to every metric being - * graphed, unless specific metrics override it. - */ - yAxis?: pulumi.Input; -} - -export interface YAxis { - /** Optional min and max settings for the left Y-axis. */ - left?: MinMax; - - /** Optional min and max settings for the right Y-axis. */ - right?: MinMax; -} - -export interface MinMax { - /** The minimum value for this Y-axis */ - min?: number; - /** The maximum value for this Y-axis */ - max?: number; -} - - -/** - * Base type for widets that display metrics as a graph (either a line or stacked graph). - * - * See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/graph_metrics.html for more - * details. - */ -export abstract class GraphMetricWidget extends MetricWidget { - constructor(private readonly graphArgs: GraphMetricWidgetArgs) { - super(graphArgs); - } - - protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "timeSeries"; - protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => this.graphArgs.yAxis; -} - -/** - * Displays a set of metrics as a line graph. - */ -export class LineGraphMetricWidget extends GraphMetricWidget { - constructor(args: GraphMetricWidgetArgs) { - super(args); - } - - protected computedStacked = () => false; -} - -/** - * Displays a set of metrics as a stacked area graph. - */ -export class StackedAreaGraphMetricWidget extends GraphMetricWidget { - constructor(args: GraphMetricWidgetArgs) { - super(args); - } - - protected computedStacked = () => true; -} - -/** - * Displays a set of metrics as a single number. - */ -export class SingleNumberMetricWidget extends MetricWidget { - constructor(args: MetricWidgetArgs) { - super(args); - } - - protected computedStacked = () => false; - protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "singleValue"; - protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => undefined; -} +// Copyright 2016-2018, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as pulumi from "@pulumi/pulumi"; +import * as wjson from "./widgets_json"; + +import { MetricWidget, MetricWidgetArgs } from "./widgets_simple"; + +type DeepRequired = { + [K in keyof T]: Required> +} +// Contains all the classes for easily making graph widgets in a dashboard. + +export interface GraphMetricWidgetArgs extends MetricWidgetArgs { + /** + * Limits for the minimums and maximums of the y-axis. This applies to every metric being + * graphed, unless specific metrics override it. + */ + yAxis?: pulumi.Input; +} + +export interface GaugeMetricWidgetArgs extends MetricWidgetArgs { + /** + * Limits for the minimums and maximums of the y-axis. Needed for Gauge Widget. + */ + yAxis: pulumi.Input>; +} + + +export interface YAxis { + /** Optional min and max settings for the left Y-axis. */ + left?: MinMax; + + /** Optional min and max settings for the right Y-axis. */ + right?: MinMax; +} + +export interface MinMax { + /** The minimum value for this Y-axis */ + min?: number; + /** The maximum value for this Y-axis */ + max?: number; +} + + +/** + * Base type for widets that display metrics as a graph (either a line or stacked graph). + * + * See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/graph_metrics.html for more + * details. + */ +export abstract class GraphMetricWidget extends MetricWidget { + constructor(private readonly graphArgs: GraphMetricWidgetArgs) { + super(graphArgs); + } + + protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "timeSeries"; + protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => this.graphArgs.yAxis; +} + +/** + * Displays a set of metrics as a line graph. + */ +export class LineGraphMetricWidget extends GraphMetricWidget { + constructor(args: GraphMetricWidgetArgs) { + super(args); + } + + protected computedStacked = () => false; +} + +/** + * Displays a set of metrics as a stacked area graph. + */ +export class StackedAreaGraphMetricWidget extends GraphMetricWidget { + constructor(args: GraphMetricWidgetArgs) { + super(args); + } + + protected computedStacked = () => true; +} + +/** + * Displays a set of metrics as a single number. + */ +export class SingleNumberMetricWidget extends MetricWidget { + constructor(args: MetricWidgetArgs) { + super(args); + } + + protected computedStacked = () => false; + protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "singleValue"; + protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => undefined; +} + +/** + * Displays a set of metrics as a gauge number. + */ +export class GaugeMetricWidget extends MetricWidget { + constructor(private readonly gaugeArgs: GaugeMetricWidgetArgs) { + super(gaugeArgs); + } + + protected computedStacked = () => false; + protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "gauge"; + protected computeYAxis = (): wjson.MetricWidgetPropertiesJson["yAxis"] => this.gaugeArgs.yAxis; +} diff --git a/sdk/nodejs/classic/cloudwatch/widgets_json.ts b/sdk/nodejs/classic/cloudwatch/widgets_json.ts index d77615dcc..dde0f12a4 100644 --- a/sdk/nodejs/classic/cloudwatch/widgets_json.ts +++ b/sdk/nodejs/classic/cloudwatch/widgets_json.ts @@ -58,7 +58,7 @@ export interface MetricWidgetPropertiesJson { period: pulumi.Input | undefined; region: pulumi.Input; stat: pulumi.Input; - view: pulumi.Input<"timeSeries" | "singleValue" | undefined>; + view: pulumi.Input<"timeSeries" | "singleValue" | "gauge" | undefined>; stacked: pulumi.Input; yAxis: pulumi.Input | undefined; } diff --git a/sdk/nodejs/classic/cloudwatch/widgets_simple.ts b/sdk/nodejs/classic/cloudwatch/widgets_simple.ts index 7e8265e67..0c6fcefe9 100644 --- a/sdk/nodejs/classic/cloudwatch/widgets_simple.ts +++ b/sdk/nodejs/classic/cloudwatch/widgets_simple.ts @@ -397,7 +397,7 @@ export class LogWidget extends SimpleWidget { protected computeType(): wjson.LogWidgetJson["type"] { return "log"; } - protected computeView = (): wjson.MetricWidgetPropertiesJson["view"] => "timeSeries"; + protected computeView = (): wjson.LogWidgetPropertiesJson["view"] => "timeSeries"; protected computedStacked = () => false; protected computeProperties(region: pulumi.Output): wjson.LogWidgetJson["properties"] {