diff --git a/dashboards/bitmovin/dashboard.json b/dashboards/bitmovin/dashboard.json
new file mode 100644
index 0000000000..f16e5d14ee
--- /dev/null
+++ b/dashboards/bitmovin/dashboard.json
@@ -0,0 +1,179 @@
+{
+ "name": "Bitmovin Analytics",
+ "description": null,
+ "pages": [
+ {
+ "name": "Bitmovin",
+ "description": null,
+ "widgets": [
+ {
+ "title": "",
+ "layout": {
+ "column": 1,
+ "row": 1,
+ "width": 12,
+ "height": 1
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.markdown"
+ },
+ "rawConfiguration": {
+ "text": "# To query more metrics, or add dimensions, modify your integration configuration following [these steps](https://github.com/newrelic/newrelic-bitmovin-analytics/tree/feat/refactor?tab=readme-ov-file#query-configuration)\n"
+ }
+ },
+ {
+ "title": "All Bitmovin Metrics",
+ "layout": {
+ "column": 1,
+ "row": 2,
+ "width": 4,
+ "height": 4
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.table"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM Metric SELECT uniques(metricName) WHERE metricName like 'bitmovin%'"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Concurrent Viewers",
+ "layout": {
+ "column": 5,
+ "row": 2,
+ "width": 8,
+ "height": 3
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.stacked-bar"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM Metric SELECT max(bitmovin.max_concurrent_viewers) as 'Concurrent Viewrs' TIMESERIES 1 minute"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Play Attemps",
+ "layout": {
+ "column": 5,
+ "row": 5,
+ "width": 8,
+ "height": 3
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.line"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM Metric SELECT max(bitmovin.cnt_play_attempts) as 'Play Attempts' TIMESERIES 1 minute since 15 minutes ago"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ },
+ "thresholds": {
+ "isLabelVisible": true
+ },
+ "yAxisLeft": {
+ "zero": true
+ },
+ "yAxisRight": {
+ "zero": true
+ }
+ }
+ },
+ {
+ "title": "All Bitmovin Dimensions",
+ "layout": {
+ "column": 1,
+ "row": 6,
+ "width": 4,
+ "height": 5
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.json"
+ },
+ "rawConfiguration": {
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM Metric SELECT keyset() WHERE metricName like 'bitmovin%'"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Video Startup Time (Histogram)",
+ "layout": {
+ "column": 5,
+ "row": 8,
+ "width": 8,
+ "height": 3
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.histogram"
+ },
+ "rawConfiguration": {
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM Metric SELECT histogram(bitmovin.cnt_video_startuptime/1000,width: 1, buckets: 100) as 'Video Startup Time (s)'"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ },
+ "yAxisLeft": {
+ "zero": true
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "variables": []
+}
\ No newline at end of file
diff --git a/dashboards/bitmovin/dashboard.png b/dashboards/bitmovin/dashboard.png
new file mode 100644
index 0000000000..8fa56cf17f
Binary files /dev/null and b/dashboards/bitmovin/dashboard.png differ
diff --git a/dashboards/slow-nrql-queries/slow-nrql-queries.json b/dashboards/slow-nrql-queries/slow-nrql-queries.json
new file mode 100644
index 0000000000..6251c8646e
--- /dev/null
+++ b/dashboards/slow-nrql-queries/slow-nrql-queries.json
@@ -0,0 +1,793 @@
+{
+ "name": "Slow NRQL Queries and Timeouts",
+ "description": null,
+ "pages": [
+ {
+ "name": "Overview",
+ "description": null,
+ "widgets": [
+ {
+ "title": "Timeouts by Event Type",
+ "layout": {
+ "column": 1,
+ "row": 1,
+ "width": 5,
+ "height": 4
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.bar"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "SELECT count(*) FROM NrdbQuery WHERE status = 'timedout' FACET query.eventType limit 100"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Long Running (> 30 secs) By Event Type",
+ "layout": {
+ "column": 6,
+ "row": 1,
+ "width": 7,
+ "height": 4
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.table"
+ },
+ "rawConfiguration": {
+ "dataFormatters": [
+ {
+ "name": "Avg Duration in Secs",
+ "precision": 2,
+ "type": "decimal"
+ }
+ ],
+ "facet": {
+ "showOtherSeries": false
+ },
+ "initialSorting": {
+ "direction": "desc",
+ "name": "Avg Duration in Secs"
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT average(durationMs) / 1000 as 'Avg Duration in Secs' where durationMs > 30000 and status != 'timedout' FACET query.eventType LIMIT MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ },
+ "thresholds": [
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 30,
+ "severity": "warning",
+ "to": 39.999
+ },
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 40,
+ "severity": "severe",
+ "to": 49.999
+ },
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 50,
+ "severity": "critical",
+ "to": 80
+ }
+ ]
+ }
+ },
+ {
+ "title": "Long Running (> 30 secs) By Event Type & Time Window",
+ "layout": {
+ "column": 1,
+ "row": 5,
+ "width": 12,
+ "height": 4
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.table"
+ },
+ "rawConfiguration": {
+ "dataFormatters": [
+ {
+ "name": "Avg Duration in Secs",
+ "precision": 2,
+ "type": "decimal"
+ }
+ ],
+ "facet": {
+ "showOtherSeries": false
+ },
+ "initialSorting": {
+ "direction": "desc",
+ "name": "Avg Duration in Secs"
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery \n WITH timeWindowMinutes/86400 AS timeWindowDays\nSELECT average(durationMs) / 1000 as 'Avg Duration in Secs' where durationMs > 30000 WHERE status != 'timedout' FACET query.eventType, timeWindowMinutes, timeWindowDays LIMIT MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ },
+ "thresholds": [
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 30,
+ "severity": "warning",
+ "to": 39.999
+ },
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 40,
+ "severity": "severe",
+ "to": 49.999
+ },
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 50,
+ "severity": "critical",
+ "to": 80
+ }
+ ]
+ }
+ },
+ {
+ "title": "Long Running (> 30 secs) Queries Stats",
+ "layout": {
+ "column": 1,
+ "row": 9,
+ "width": 12,
+ "height": 5
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.table"
+ },
+ "rawConfiguration": {
+ "dataFormatters": [
+ {
+ "name": "Avg Duration in Secs",
+ "precision": 2,
+ "type": "decimal"
+ }
+ ],
+ "facet": {
+ "showOtherSeries": false
+ },
+ "initialSorting": {
+ "direction": "desc",
+ "name": "Avg Duration in Secs"
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT average(durationMs) / 1000 as 'Avg Duration in Secs' where durationMs > 30000 and status != 'timedout' FACET query LIMIT MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ },
+ "thresholds": [
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 30,
+ "severity": "warning",
+ "to": 39.999
+ },
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 40,
+ "severity": "severe",
+ "to": 49.999
+ },
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 50,
+ "severity": "critical",
+ "to": 80
+ }
+ ]
+ }
+ },
+ {
+ "title": "",
+ "layout": {
+ "column": 1,
+ "row": 14,
+ "width": 2,
+ "height": 5
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.markdown"
+ },
+ "rawConfiguration": {
+ "text": "# Reference\n## Source Types:\n- apm: Application performance monitoring\n- dashboard: Dashboards displayed on the New Relic platform\n- Distributed tracing: Distributed tracing\n- nerdgraph: NerdGraph, our GraphQL API\n- New Relic user interface: A New Relic curated UI\n- query-api: Insights query API\n- query-builder: Query builder\n [more](https://docs.newrelic.com/attribute-dictionary/?event=NrdbQuery&attribute=source.name)\n---\n### Data Types —[data types](https://docs.newrelic.com/attribute-dictionary/).\nlet me know if you have question: srohatgi@newrelic.com"
+ }
+ },
+ {
+ "title": "Queries That Timed Out Stats",
+ "layout": {
+ "column": 3,
+ "row": 14,
+ "width": 10,
+ "height": 5
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.bar"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT count(*) where status = 'timedout' FACET nr.source, query LIMIT MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Successful Execuations",
+ "layout": {
+ "column": 1,
+ "row": 19,
+ "width": 6,
+ "height": 3
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.line"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT ((count(query) - filter(count(query), WHERE status !='successful' AND status !='error')) / count(query)) * 100 AS '% Successful' FACET cases(WHERE query LIKE '%bytecountestimate()%' AS 'bytecountestimate()', WHERE query NOT LIKE '%bytecountestimate()%' AS 'Normal') TIMESERIES 30 minutes EXTRAPOLATE "
+ }
+ ],
+ "nullValues": {
+ "nullValue": "remove"
+ },
+ "platformOptions": {
+ "ignoreTimeRange": false
+ },
+ "thresholds": {
+ "isLabelVisible": true
+ },
+ "yAxisLeft": {
+ "zero": false
+ },
+ "yAxisRight": {
+ "zero": true
+ }
+ }
+ },
+ {
+ "title": "Timedout",
+ "layout": {
+ "column": 7,
+ "row": 19,
+ "width": 6,
+ "height": 3
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.line"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "SELECT filter(count(query), WHERE query LIKE '%bytecountestimate()%') AS bytecountestimate, filter(count(query), WHERE query NOT LIKE '%bytecountestimate()%' ) as 'Normal' FROM NrdbQuery WHERE status = 'timedout' TIMESERIES 30 minutes FACET status "
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ },
+ "thresholds": {
+ "isLabelVisible": true
+ },
+ "yAxisLeft": {
+ "zero": true
+ },
+ "yAxisRight": {
+ "zero": true
+ }
+ }
+ },
+ {
+ "title": "Errors",
+ "layout": {
+ "column": 1,
+ "row": 22,
+ "width": 6,
+ "height": 3
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.stacked-bar"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "SELECT filter(count(query), WHERE query LIKE '%bytecountestimate()%') AS bytecountestimate, filter(count(query), WHERE query NOT LIKE '%bytecountestimate()%' ) as 'Normal' FROM NrdbQuery WHERE status = 'error' TIMESERIES 30 minutes FACET status "
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "NRDB query API requests per minute",
+ "layout": {
+ "column": 7,
+ "row": 22,
+ "width": 6,
+ "height": 3
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.line"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM Metric SELECT sum(newrelic.resourceConsumption.currentValue) AS 'Usage' WHERE limitName = 'NRDB query API requests per minute' TIMESERIES AUTO "
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ },
+ "thresholds": {
+ "isLabelVisible": true
+ },
+ "yAxisLeft": {
+ "zero": true
+ },
+ "yAxisRight": {
+ "zero": true
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Detail Stats",
+ "description": null,
+ "widgets": [
+ {
+ "title": "Log Query Performance MoM (Time Window >= 1 day)",
+ "layout": {
+ "column": 1,
+ "row": 1,
+ "width": 6,
+ "height": 5
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.billboard"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "SELECT FILTER(COUNT(*), WHERE timeWindowMinutes >= 1440 AND query.eventType LIKE '%') AS queries, FILTER(COUNT(*), WHERE status = 'timedout' AND timeWindowMinutes >= 1440 AND query.eventType LIKE '%') AS query_timedout, FILTER(COUNT(*), WHERE status = 'error' AND timeWindowMinutes >= 1440 AND query.eventType LIKE '%') AS query_error, FILTER(MEDIAN(durationMs) / 1000, WHERE timeWindowMinutes >= 1440 AND query.eventType LIKE '%') AS median_duration_sec, FILTER(MEDIAN(timeWindowMinutes) / 60, WHERE timeWindowMinutes >= 1440 AND query.eventType LIKE '%') AS median_window_hours, FILTER(MEDIAN(inspectedCount), WHERE timeWindowMinutes >= 1440 AND query.eventType LIKE '%') AS inspectedCounts, FILTER(MEDIAN((inspectedCount / 1000000) / (durationMs / 1000)), WHERE timeWindowMinutes >= 1440 AND query.eventType LIKE '%') AS meps FROM NrdbQuery LIMIT MAX since 1 month ago compare with 1 month ago"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Log Query Performance MoM (Time Window < 1 day)",
+ "layout": {
+ "column": 7,
+ "row": 1,
+ "width": 6,
+ "height": 5
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.billboard"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "SELECT FILTER(COUNT(*), WHERE timeWindowMinutes < 1440 AND query.eventType LIKE '%') AS queries, FILTER(COUNT(*), WHERE status = 'timedout' AND timeWindowMinutes < 1440 AND query.eventType LIKE '%') AS query_timedout, FILTER(COUNT(*), WHERE status = 'error' AND timeWindowMinutes < 1440 AND query.eventType LIKE '%') AS query_error, FILTER(MEDIAN(durationMs) / 1000, WHERE timeWindowMinutes < 1440 AND query.eventType LIKE '%') AS median_duration_sec, FILTER(MEDIAN(timeWindowMinutes) / 60, WHERE timeWindowMinutes < 1440 AND query.eventType LIKE '%') AS median_window_hours, FILTER(MEDIAN(inspectedCount), WHERE timeWindowMinutes < 1440 AND query.eventType LIKE '%') AS inspectedCounts, FILTER(MEDIAN((inspectedCount / 1000000) / (durationMs / 1000)), WHERE timeWindowMinutes < 1440 AND query.eventType LIKE '%') AS meps FROM NrdbQuery LIMIT MAX since 1 month ago compare with 1 month ago UNTIL now"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Timedout Queries (TimeWindow >= 1 day) Past 7 Days",
+ "layout": {
+ "column": 1,
+ "row": 6,
+ "width": 6,
+ "height": 3
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.stacked-bar"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "SELECT count(*) FROM NrdbQuery WHERE status = 'timedout' AND timeWindowMinutes >= 1440 TIMESERIES auto FACET productCapability since 7 days ago"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Timedout Queries (TimeWindow < 1 day) Past 7 Days",
+ "layout": {
+ "column": 7,
+ "row": 6,
+ "width": 6,
+ "height": 3
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.stacked-bar"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "SELECT count(*) FROM NrdbQuery WHERE status = 'timedout' AND timeWindowMinutes < 1440 TIMESERIES auto FACET productCapability since 7 days ago"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Long Running (> 10 secs) Log Queries Stats",
+ "layout": {
+ "column": 1,
+ "row": 9,
+ "width": 12,
+ "height": 5
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.table"
+ },
+ "rawConfiguration": {
+ "dataFormatters": [
+ {
+ "name": "Avg Duration in Secs",
+ "precision": 2,
+ "type": "decimal"
+ }
+ ],
+ "facet": {
+ "showOtherSeries": false
+ },
+ "initialSorting": {
+ "direction": "desc",
+ "name": "Executions"
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT average(durationMs) / 1000 as 'Avg Duration in Secs', average(inspectedCount) as 'Inspected Count', count(*) as 'Executions', average(timeWindowMinutes)/60/24 as 'Time Window (days)' where durationMs > 10000 and status != 'timedout' FACET query LIMIT MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ },
+ "thresholds": [
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 20,
+ "severity": "warning",
+ "to": 40
+ },
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 40,
+ "severity": "severe",
+ "to": 70
+ },
+ {
+ "columnName": "Avg Duration in Secs",
+ "from": 70,
+ "severity": "critical",
+ "to": 60000
+ }
+ ]
+ }
+ },
+ {
+ "title": "",
+ "layout": {
+ "column": 1,
+ "row": 14,
+ "width": 2,
+ "height": 5
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.markdown"
+ },
+ "rawConfiguration": {
+ "text": "# Reference\n## Source Types:\n- apm: Application performance monitoring\n- dashboard: Dashboards displayed on the New Relic platform\n- Distributed tracing: Distributed tracing\n- nerdgraph: NerdGraph, our GraphQL API\n- New Relic user interface: A New Relic curated UI\n- query-api: Insights query API\n- query-builder: Query builder\n [more](https://docs.newrelic.com/attribute-dictionary/?event=NrdbQuery&attribute=source.name)\n---\n### Data Types —[data types](https://docs.newrelic.com/attribute-dictionary/).\nlet me know if you have question: srohatgi@newrelic.com"
+ }
+ },
+ {
+ "title": "Queries That Timed Out Stats",
+ "layout": {
+ "column": 3,
+ "row": 14,
+ "width": 10,
+ "height": 5
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.bar"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT count(*) where status = 'timedout' FACET nr.source, query LIMIT MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Users with Slow Queries [Duration 20+ seconds & Time window 1+ days]",
+ "layout": {
+ "column": 1,
+ "row": 19,
+ "width": 12,
+ "height": 8
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.pie"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "legend": {
+ "enabled": true
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT average(timeWindowMinutes)/60/24 as 'Time Window (days)' where durationMs > 20000 and status != 'timedout' and query LIKE '%from log%' and timeWindowMinutes > 1440 FACET user LIMIT 10"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Scanned Events",
+ "description": null,
+ "widgets": [
+ {
+ "title": "Scanned Events (estimate) By Query and Source URL",
+ "layout": {
+ "column": 1,
+ "row": 1,
+ "width": 12,
+ "height": 8
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.table"
+ },
+ "rawConfiguration": {
+ "dataFormatters": [
+ {
+ "name": "Duration in Secs",
+ "precision": 2,
+ "type": "decimal"
+ },
+ {
+ "name": "Events Scanned",
+ "type": "humanized"
+ }
+ ],
+ "facet": {
+ "showOtherSeries": true
+ },
+ "initialSorting": {
+ "direction": "desc",
+ "name": "Events Scanned"
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrComputeUsage SELECT sum((usage)*1.21*1000000000) as 'estimated avg. scanned events' facet dimension_productCapability, dimension_sourceUrl, dimension_query where (usage > 1.21) limit MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Dashboard Usage (unsucsessful)",
+ "description": null,
+ "widgets": [
+ {
+ "title": "Impacted Dashboards",
+ "layout": {
+ "column": 1,
+ "row": 1,
+ "width": 12,
+ "height": 6
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.table"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT count(*) WHERE source.dashboardId IS NOT NULL and status != 'successful' FACET status, source.url, source.dashboardId, source.name LIMIT MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Impacted Dashboard Users",
+ "layout": {
+ "column": 1,
+ "row": 7,
+ "width": 12,
+ "height": 6
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.table"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT count(*) WHERE source.dashboardId IS NOT NULL and status != 'successful' FACET user, status LIMIT MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ },
+ {
+ "title": "Impacted Dashboard Queries (By User)",
+ "layout": {
+ "column": 1,
+ "row": 13,
+ "width": 12,
+ "height": 6
+ },
+ "linkedEntityGuids": null,
+ "visualization": {
+ "id": "viz.table"
+ },
+ "rawConfiguration": {
+ "facet": {
+ "showOtherSeries": false
+ },
+ "nrqlQueries": [
+ {
+ "accountIds": [],
+ "query": "FROM NrdbQuery SELECT count(*) WHERE source.dashboardId IS NOT NULL and status != 'successful' FACET user, status, source.url, source.dashboardId, source.name, query, query.eventType LIMIT MAX"
+ }
+ ],
+ "platformOptions": {
+ "ignoreTimeRange": false
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "variables": []
+}
diff --git a/dashboards/slow-nrql-queries/slow-nrql-queries.png b/dashboards/slow-nrql-queries/slow-nrql-queries.png
new file mode 100644
index 0000000000..1d94bdc886
Binary files /dev/null and b/dashboards/slow-nrql-queries/slow-nrql-queries.png differ
diff --git a/data-sources/bitmovin/config.yml b/data-sources/bitmovin/config.yml
new file mode 100644
index 0000000000..161979876a
--- /dev/null
+++ b/data-sources/bitmovin/config.yml
@@ -0,0 +1,21 @@
+id: bitmovin-video
+displayName: Bitmovin
+description: |
+ Bitmovin empowers businesses to expand their viewer reach and stream video globally with unmatched reliability, scalability, and the highest quality across the broadest range of devices.
+icon: logo.svg
+install:
+ primary:
+ link:
+ url: https://github.com/newrelic/newrelic-bitmovin-analytics
+keywords:
+ - bitmovin
+ - video quality
+ - video experience
+ - quality of experience
+ - video streaming
+ - video metrics
+ - featured
+ - newrelic partner
+ - NR1_addData
+categoryTerms:
+ - newrelic partner
diff --git a/data-sources/bitmovin/logo.svg b/data-sources/bitmovin/logo.svg
new file mode 100644
index 0000000000..64d8424410
--- /dev/null
+++ b/data-sources/bitmovin/logo.svg
@@ -0,0 +1,40 @@
+
+
+
diff --git a/quickstarts/bitmovin/config.yml b/quickstarts/bitmovin/config.yml
new file mode 100644
index 0000000000..55a471c416
--- /dev/null
+++ b/quickstarts/bitmovin/config.yml
@@ -0,0 +1,34 @@
+id: 249d6a1f-091b-4c63-ad22-d8f1b3a900f9
+slug: bitmovin-video
+description: |
+ ## What is Bitmovin?
+
+ Bitmovin empowers businesses to expand their viewer reach and stream video globally with unmatched reliability, scalability, and the highest quality across the broadest range of devices.
+
+ ### Get started!
+
+ The Bitmovin - New Relic integration utilizes the Bitmovin Analytics API to get data into New Relic.
+
+ Check out the [documentation](https://github.com/newrelic/newrelic-bitmovin-analytics/blob/master/README.md) to learn more about setting up New Relic monitoring for of data from Bitmovin.
+summary: |
+ Bitmovin empowers businesses to expand their viewer reach and stream video globally with unmatched reliability, scalability, and the highest quality across the broadest range of devices.
+icon: logo.svg
+level: Community
+authors:
+ - Chris McCarthy
+title: Bitmovin Analytics
+dataSourceIds:
+ - bitmovin-video
+dashboards:
+ - bitmovin
+documentation:
+ - name: Bitmovin integration installation docs
+ description: Pull specified metrics and dimensions from the Bitmovin Analytics API into New Relic
+ url: https://github.com/newrelic/newrelic-bitmovin-analytics/blob/master/README.md
+keywords:
+ - bitmovin
+ - video
+ - streaming
+ - newrelic partner
+ - NR1_addData
+ - NR1_sys
diff --git a/quickstarts/bitmovin/logo.svg b/quickstarts/bitmovin/logo.svg
new file mode 100644
index 0000000000..64d8424410
--- /dev/null
+++ b/quickstarts/bitmovin/logo.svg
@@ -0,0 +1,40 @@
+
+
+
diff --git a/quickstarts/slow-nrql-query-analytics/config.yml b/quickstarts/slow-nrql-query-analytics/config.yml
new file mode 100644
index 0000000000..085e6c6132
--- /dev/null
+++ b/quickstarts/slow-nrql-query-analytics/config.yml
@@ -0,0 +1,27 @@
+id: 47436e4a-f74c-4d87-8756-afc203a0f0d9
+slug: slow-nrql-query-analytics
+title: Slow Query Analytics
+description: |
+ This quickstart provides resources to better understand your NRQL query performance.
+summary: |
+ Find out what kinds of telemetry and query patterns are responsible for slower queries. The dashbaords in this quickstart will help you pinpoint hot spots and opitimze your queries.
+level: New Relic
+authors:
+ - Marcel Schlapfer
+ - Jim Hagan
+keywords:
+ - NRQL
+ - Slow Queries
+ - Query Performance
+ - Query Optimization
+ - Query Timeouts
+dataSourceIds:
+ - solutions-hub-dashboards
+documentation:
+ - name: NRQL Query Performance
+ url: https://docs.newrelic.com/docs/nrql/nrql-syntax-clauses-functions/
+ description: Find slow query patterns and optimize your queries.
+icon: logo.svg
+
+dashboards:
+ - slow-nrql-queries
diff --git a/quickstarts/slow-nrql-query-analytics/logo.svg b/quickstarts/slow-nrql-query-analytics/logo.svg
new file mode 100644
index 0000000000..ea60419c6b
--- /dev/null
+++ b/quickstarts/slow-nrql-query-analytics/logo.svg
@@ -0,0 +1,13 @@
+
diff --git a/utils/__tests__/build-validate-quickstart-artifact.test.js b/utils/__tests__/build-validate-quickstart-artifact.test.js
index 4bb77a6b97..f6a809f757 100644
--- a/utils/__tests__/build-validate-quickstart-artifact.test.js
+++ b/utils/__tests__/build-validate-quickstart-artifact.test.js
@@ -1,20 +1,36 @@
-import * as fs from 'fs';
+import * as path from 'path';
import Quickstart from '../lib/Quickstart';
import DataSource from '../lib/DataSource';
import Alert from '../lib/Alert';
import Dashboard from '../lib/Dashboard';
-
import {
getArtifactComponents,
- getDataSourceIds,
validateArtifact,
} from '../build-validate-quickstart-artifact';
-jest.mock('../lib/Quickstart');
-jest.mock('../lib/DataSource');
-jest.mock('../lib/Alert');
-jest.mock('../lib/Dashboard');
-jest.mock('fs');
+const MOCK_FILES_BASEPATH = path.resolve(__dirname, '..', 'mock_files');
+
+const mockQuickstart1 = new Quickstart(
+ '/quickstarts/mock-quickstart-1/config.yml',
+ MOCK_FILES_BASEPATH
+);
+
+const mockQuickstart2 = new Quickstart(
+ 'quickstarts/mock-quickstart-2/config.yml',
+ MOCK_FILES_BASEPATH
+);
+
+const mockDataSource1 = new DataSource(
+ 'test-data-source',
+ MOCK_FILES_BASEPATH
+)
+
+const mockAlert1 = new Alert(
+ 'mock-alert-policy-1',
+ MOCK_FILES_BASEPATH
+);
+
+const mockDashboard1 = new Dashboard('mock-dashboard-1', MOCK_FILES_BASEPATH);
describe('built-validate-quickstart-artifact', () => {
beforeEach(() => {
@@ -26,58 +42,27 @@ describe('built-validate-quickstart-artifact', () => {
it('should find all of the components', () => {
Quickstart.getAll = jest
.fn()
- .mockReturnValueOnce([
- { config: 'test-quickstart-1' },
- { config: 'test-quickstart-2' },
- ]);
+ .mockReturnValueOnce([mockQuickstart1, mockQuickstart2]);
- DataSource.getAll = jest
- .fn()
- .mockReturnValueOnce([{ config: 'test-dataSource-1' }]);
+ DataSource.getAll = jest.fn().mockReturnValueOnce([mockDataSource1]);
- Alert.getAll = jest
- .fn()
- .mockReturnValueOnce([{ config: 'test-alert-1' }]);
- Dashboard.getAll = jest
- .fn()
- .mockReturnValueOnce([{ config: 'test-dashboard-1' }]);
+ Alert.getAll = jest.fn().mockReturnValueOnce([mockAlert1]);
+ Dashboard.getAll = jest.fn().mockReturnValueOnce([mockDashboard1]);
const actual = getArtifactComponents();
expect(actual.quickstarts).toHaveLength(2);
- expect(actual.quickstarts[0]).toEqual('test-quickstart-1');
- expect(actual.quickstarts[1]).toEqual('test-quickstart-2');
- expect(actual.dataSources).toHaveLength(1);
- expect(actual.dataSources[0]).toEqual('test-dataSource-1');
- expect(actual.alerts).toHaveLength(1);
- expect(actual.alerts[0]).toEqual('test-alert-1');
- expect(actual.dashboards).toHaveLength(1);
- expect(actual.dashboards[0]).toEqual('test-dashboard-1');
- });
+ expect(actual.quickstarts[0].dashboards).toEqual([]);
+ expect(actual.quickstarts[1].dashboards).toEqual(['mock-dashboard-1']);
- it('should produce a complete list of dataSource IDs', () => {
- Quickstart.getAll = jest.fn().mockReturnValueOnce([]);
- Alert.getAll = jest.fn().mockReturnValueOnce([]);
- Dashboard.getAll = jest.fn().mockReturnValueOnce([]);
- DataSource.getAll = jest
- .fn()
- .mockReturnValueOnce([
- { config: { id: 'community-1' } },
- { config: { id: 'community-2' } },
- { config: { id: 'community-3' } },
- ]);
-
- const { dataSources } = getArtifactComponents();
- fs.readFileSync.mockReturnValueOnce(JSON.stringify(['core-1', 'core-2']));
+ expect(actual.dataSources).toHaveLength(1);
+ expect(actual.dataSources[0].id).toEqual('test-data-source');
- const actual = getDataSourceIds('dummy-file.json', dataSources);
+ expect(Object.keys(actual.alerts)).toHaveLength(1);
+ expect(Object.keys(actual.alerts)).toContain('mock-alert-policy-1');
- expect(actual).toHaveLength(5);
- expect(actual).toContain('community-1');
- expect(actual).toContain('community-2');
- expect(actual).toContain('community-3');
- expect(actual).toContain('core-1');
- expect(actual).toContain('core-2');
+ expect(Object.keys(actual.dashboards)).toHaveLength(1);
+ expect(Object.keys(actual.dashboards)).toContain('mock-dashboard-1');
});
});
@@ -86,8 +71,8 @@ describe('built-validate-quickstart-artifact', () => {
type: 'object',
properties: {
quickstarts: { type: 'array' },
- alerts: { type: 'array' },
- dashboards: { type: 'array' },
+ alerts: { type: 'object' },
+ dashboards: { type: 'object' },
dataSources: {
type: 'array',
items: {
@@ -109,20 +94,12 @@ describe('built-validate-quickstart-artifact', () => {
DataSource.getAll = jest
.fn()
.mockReturnValueOnce([
- { config: { id: 'community-1', title: 'DataSource 1' } },
- { config: { id: 'community-2', title: 'DataSource 2' } },
- { config: { id: 'community-3', title: 'DataSource 3' } },
+ mockDataSource1
]);
const components = getArtifactComponents();
- fs.readFileSync.mockReturnValueOnce(JSON.stringify(['core-1', 'core-2']));
- const dataSourceIds = getDataSourceIds(
- 'dummy-file.json',
- components.dataSources
- );
-
- const artifact = { ...components, dataSourceIds };
+ const artifact = { ...components, dataSourceIds: ['core-1', 'core-2'] };
const actual = validateArtifact(TEST_SCHEMA, artifact);
@@ -133,23 +110,19 @@ describe('built-validate-quickstart-artifact', () => {
Quickstart.getAll = jest.fn().mockReturnValueOnce([]);
Alert.getAll = jest.fn().mockReturnValueOnce([]);
Dashboard.getAll = jest.fn().mockReturnValueOnce([]);
- DataSource.getAll = jest
- .fn()
- .mockReturnValueOnce([
- { config: { id: 'community-1', title: 'DataSource 1' } },
- { config: { id: false, title: 'DataSource 2' } },
- { config: { id: 'community-3', title: 3 } },
- ]);
+ DataSource.getAll = jest.fn().mockReturnValueOnce([]);
const components = getArtifactComponents();
- fs.readFileSync.mockReturnValueOnce(JSON.stringify(['core-1', 'core-2']));
- const dataSourceIds = getDataSourceIds(
- 'dummy-file.json',
- components.dataSources
- );
-
- const artifact = { ...components, dataSourceIds };
+ const artifact = {
+ ...components,
+ dataSources: [
+ { id: 'community-1', title: 'DataSource 1' },
+ { id: false, title: 'DataSource 2' },
+ { id: 'community-3', title: 3 },
+ ],
+ dataSourceIds: ['core-1', 'core-2'],
+ };
const actual = validateArtifact(TEST_SCHEMA, artifact);
diff --git a/utils/build-validate-quickstart-artifact.ts b/utils/build-validate-quickstart-artifact.ts
index 46b88070a2..7a6480c90d 100644
--- a/utils/build-validate-quickstart-artifact.ts
+++ b/utils/build-validate-quickstart-artifact.ts
@@ -5,11 +5,10 @@ import get from 'lodash/get';
import Quickstart from "./lib/Quickstart";
import DataSource from "./lib/DataSource";
import Alert from "./lib/Alert";
-import Dashboard, { DashboardConfig } from "./lib/Dashboard";
+import Dashboard from "./lib/Dashboard";
import Ajv, { type ErrorObject } from 'ajv';
-import { QuickstartConfig, QuickstartConfigAlert } from './types/QuickstartConfig';
-import { DataSourceConfig } from './types/DataSourceConfig';
import { passedProcessArguments } from './lib/helpers';
+import { ArtifactDataSourceConfig, ArtifactDashboardConfig, ArtifactQuickstartConfig, ArtifactAlertConfig } from './types/Artifact';
type ArtifactSchema = Record;
@@ -20,10 +19,10 @@ type InvalidItem = {
}
type ArtifactComponents = {
- quickstarts: QuickstartConfig[],
- dataSources: DataSourceConfig[],
- alerts: QuickstartConfigAlert[][],
- dashboards: DashboardConfig[]
+ quickstarts: ArtifactQuickstartConfig[],
+ dataSources: ArtifactDataSourceConfig[],
+ alerts: ArtifactAlertConfig,
+ dashboards: ArtifactDashboardConfig
}
type Artifact = ArtifactComponents | {
@@ -38,17 +37,33 @@ const getSchema = (filepath: string): ArtifactSchema => {
// NOTE: we could run these in parallel to speed up the script
export const getArtifactComponents = (): ArtifactComponents => {
- const quickstarts = Quickstart.getAll().map((quickstart) => quickstart.config);
+ const quickstarts = Quickstart
+ .getAll()
+ .map((quickstart) => quickstart.transformForArtifact());
+
console.log(`[*] Found ${quickstarts.length} quickstarts`);
- const dataSources = DataSource.getAll().map((dataSource) => dataSource.config);
+ const dataSources = DataSource
+ .getAll()
+ .map((dataSource) => dataSource.transformForArtifact());
+
console.log(`[*] Found ${dataSources.length} dataSources`);
- const alerts = Alert.getAll().map((alert) => alert.config);
- console.log(`[*] Found ${alerts.length} alerts`);
+ const alerts = Alert.getAll().reduce((acc, alert) => {
+ const conditions = alert.transformForArtifact()
+
+ return { ...acc, ...conditions }
+
+ }, {});
+
+ console.log(`[*] Found ${Object.keys(alerts).length} alerts`);
+
+ const dashboards = Dashboard.getAll().reduce((acc, dash) => {
+ const dashboard = dash.transformForArtifact()
+ return { ...acc, ...dashboard }
- const dashboards = Dashboard.getAll().map((dashboard) => dashboard.config);
- console.log(`[*] Found ${dashboards.length} dashboards`);
+ }, {});
+ console.log(`[*] Found ${Object.keys(dashboards).length} dashboards`);
return {
quickstarts,
@@ -58,7 +73,7 @@ export const getArtifactComponents = (): ArtifactComponents => {
}
};
-export const getDataSourceIds = (filepath: string, communityDataSources: DataSourceConfig[]): string[] => {
+export const getDataSourceIds = (filepath: string, communityDataSources: ArtifactComponents['dataSources']): string[] => {
const coreDataSourceIds = yaml.load(
fs.readFileSync(filepath).toString('utf8')
) as string[];
diff --git a/utils/lib/Alert.ts b/utils/lib/Alert.ts
index 11c81c0524..c5dadbcdb6 100644
--- a/utils/lib/Alert.ts
+++ b/utils/lib/Alert.ts
@@ -10,6 +10,7 @@ import type {
AlertType,
QuickstartAlertInput,
} from '../types/QuickstartMutationVariable';
+import type { ArtifactAlertConfig, ArtifactAlertType } from '../types/Artifact';
import type { QuickstartConfigAlert } from '../types/QuickstartConfig';
import type { NerdGraphResponseWithLocalErrors } from '../types/nerdgraph';
@@ -79,7 +80,11 @@ export type SubmitSetRequiredDataSourcesMutationResult =
| NerdGraphResponseWithLocalErrors
| { errors: ErrorOrNerdGraphError[] };
-class Alert extends Component {
+class Alert extends Component<
+ QuickstartConfigAlert[],
+ QuickstartAlertInput[],
+ ArtifactAlertConfig
+> {
constructor(identifier: string, basePath?: string) {
super(identifier, basePath);
this.isValid = this.validate();
@@ -134,6 +139,38 @@ class Alert extends Component {
}
}
+ /**
+ * Method extracts criteria from the config and returns an object appropriately
+ * structured for the artifact.
+ */
+ transformForArtifact() {
+ if (!this.isValid) {
+ console.error(
+ `Alert is invalid.\nPlease check if the path at ${this.identifier} exists.`
+ );
+ return {};
+ }
+
+ const alertPolicy = this.config.map((condition) => {
+ const { description, name, type } = condition;
+
+ return {
+ description: description && description.trim(),
+ displayName: name && name.trim(),
+ rawConfiguration: JSON.stringify(condition),
+ sourceUrl: Component.getAssetSourceUrl(this.configPath),
+ type: type && (type.trim().toLowerCase() as ArtifactAlertType),
+ };
+ });
+
+ return { [this.identifier]: alertPolicy };
+ }
+
+ /**
+ * Method creates mutation variables for a given Alert.
+ *
+ * @deprecated This function should be removed once we have finished our new build publishing pipeline
+ */
getMutationVariables() {
if (!this.isValid) {
console.error(
@@ -190,13 +227,17 @@ class Alert extends Component {
return true;
} catch (error) {
- logger.error(`Alert Condition: Validaiton for ${displayName} failed with an error`);
+ logger.error(
+ `Alert Condition: Validaiton for ${displayName} failed with an error`
+ );
return false;
}
});
- logger.debug(`Alert condition: Finished validation for alert at ${this.identifier}`);
+ logger.debug(
+ `Alert condition: Finished validation for alert at ${this.identifier}`
+ );
return validations.every(Boolean);
}
@@ -299,8 +340,10 @@ class Alert extends Component {
}
static getAll() {
- const alertPaths = glob.sync(path.join(__dirname, '..', '..', 'alert-policies', '**', '*.+(yml|yaml)'));
- return alertPaths.map(alertPath => {
+ const alertPaths = glob.sync(
+ path.join(__dirname, '..', '..', 'alert-policies', '**', '*.+(yml|yaml)')
+ );
+ return alertPaths.map((alertPath) => {
// The identifier for alerts is the folder and the file name
// e.g. `node-js/HighCpuUtilization.yml`
const identifier = path.join(...alertPath.split('/').slice(-2, -1));
diff --git a/utils/lib/Component.ts b/utils/lib/Component.ts
index 143ec16262..a6ed66002b 100644
--- a/utils/lib/Component.ts
+++ b/utils/lib/Component.ts
@@ -3,7 +3,7 @@ import * as fs from 'fs';
import * as yaml from 'js-yaml';
import { GITHUB_REPO_BASE_URL } from '../constants';
-abstract class Component {
+abstract class Component {
public identifier: string; // Local path to the component. Ex: python/flask
public configPath: string; // Absolute path to the config file within the repository
public config: ConfigType;
@@ -23,6 +23,7 @@ abstract class Component {
abstract getConfigFilePath(): string;
abstract getConfigContent(): ConfigType;
abstract getMutationVariables(): MutationVariablesType;
+ abstract transformForArtifact(): ArtifactType;
get fullPath() {
return path.join(this.basePath, this.configPath);
diff --git a/utils/lib/Dashboard.ts b/utils/lib/Dashboard.ts
index 6a846f1516..781b428d36 100644
--- a/utils/lib/Dashboard.ts
+++ b/utils/lib/Dashboard.ts
@@ -16,6 +16,7 @@ import {
fetchNRGraphqlResults,
ErrorOrNerdGraphError,
} from './nr-graphql-helpers';
+import { ArtifactDashboardConfig } from '../types/Artifact';
export interface DashboardConfig {
name: string;
@@ -61,7 +62,11 @@ export type SubmitSetRequiredDataSourcesMutationResult =
| NerdGraphResponseWithLocalErrors
| { errors: ErrorOrNerdGraphError[] };
-class Dashboard extends Component {
+class Dashboard extends Component<
+ DashboardConfig,
+ QuickstartDashboardInput,
+ ArtifactDashboardConfig
+> {
/**
* @returns - filepath from top level directory.
*/
@@ -104,6 +109,34 @@ class Dashboard extends Component {
}
}
+ /**
+ * Method extracts criteria from the config and returns an object appropriately
+ * structured for the artifact.
+ */
+ transformForArtifact() {
+ if (!this.isValid) {
+ console.error(
+ `Dashboard is invalid.\nPlease check the dashboard at ${this.identifier}\n`
+ );
+ return {};
+ }
+
+ const { name, description } = this.config;
+ const screenshotPaths = this.getScreenshotPaths();
+
+ return {
+ [this.identifier]: {
+ description: description && description.trim(),
+ displayName: name && name.trim(),
+ rawConfiguration: JSON.stringify(this.config),
+ sourceUrl: Component.getAssetSourceUrl(this.configPath),
+ screenshots:
+ screenshotPaths &&
+ screenshotPaths.map((s) => this.getScreenshotUrl(s)),
+ },
+ };
+ }
+
/**
* Get mutation variables from dashboard config
* @returns - mutation variables for dashboard.
diff --git a/utils/lib/DataSource.ts b/utils/lib/DataSource.ts
index 04ccce4b0b..e19cb65e47 100644
--- a/utils/lib/DataSource.ts
+++ b/utils/lib/DataSource.ts
@@ -17,6 +17,7 @@ import type {
DataSourceInstallDirectiveInput,
DataSourceMutationVariable,
} from '../types/DataSourceMutationVariable';
+import { ArtifactDataSourceConfig, ArtifactInstall } from '../types/Artifact';
export interface DataSourceMutationResponse {
dataSource: {
@@ -24,7 +25,11 @@ export interface DataSourceMutationResponse {
};
}
-class DataSource extends Component {
+class DataSource extends Component<
+ DataSourceConfig,
+ string,
+ ArtifactDataSourceConfig
+> {
/**
* @returns Filepath for the configuration file (from top-level directory).
*/
@@ -73,9 +78,39 @@ class DataSource extends Component {
return this._getYamlConfigContent();
}
+ /**
+ * Method extracts criteria from the config and returns an object appropriately
+ * structured for the artifact.
+ */
+ public transformForArtifact() {
+ const { keywords, description, categoryTerms, icon, ...rest } = this.config;
+
+ return {
+ ...rest,
+ iconUrl: this._getIconUrl(),
+ install: this._parseInstallsForArtifact(),
+ categoryTerms: categoryTerms ? categoryTerms.map((t) => t.trim()) : [],
+ keywords: keywords ? keywords.map((k) => k.trim()) : [],
+ description: description && description.trim(),
+ };
+ }
+
+ private _parseInstallsForArtifact() {
+ const { install } = this.config;
+
+ return {
+ primary: this._parseInstallDirectiveForArtifact(install.primary),
+ fallback:
+ install.fallback &&
+ this._parseInstallDirectiveForArtifact(install.fallback),
+ };
+ }
+
/**
* Get the variables for the **Quickstart** mutation.
*
+ * @deprecated This function should be removed once we have finished our new build publishing pipeline
+ *
* @returns The ID for this data source
*/
getMutationVariables(): DataSourceConfig['id'] {
@@ -189,6 +224,33 @@ class DataSource extends Component {
return directive;
}
+ /**
+ * Helper method that returns the directive, based on its type.
+ */
+ private _parseInstallDirectiveForArtifact(
+ directive: DataSourceConfigInstallDirective
+ ): ArtifactInstall {
+ if ('link' in directive) {
+ const { url } = directive.link;
+
+ return {
+ url: url?.trim() ?? '',
+ };
+ }
+
+ if ('nerdlet' in directive) {
+ const { nerdletId, nerdletState, requiresAccount } = directive.nerdlet;
+
+ return {
+ nerdletId: nerdletId?.trim() ?? '',
+ nerdletState: nerdletState,
+ requiresAccount: requiresAccount,
+ };
+ }
+
+ return directive;
+ }
+
static isDataSource(x: DataSource | undefined): x is DataSource {
return x !== undefined;
}
diff --git a/utils/lib/Quickstart.ts b/utils/lib/Quickstart.ts
index c8e04f8239..5bfd59f557 100644
--- a/utils/lib/Quickstart.ts
+++ b/utils/lib/Quickstart.ts
@@ -23,6 +23,7 @@ import type {
QuickstartSupportLevel,
} from '../types/QuickstartMutationVariable';
import type { QuickstartConfig } from '../types/QuickstartConfig';
+import { ArtifactQuickstartConfig, ArtifactQuickstartConfigSupportLevel } from '../types/Artifact';
export interface QuickstartMutationResponse {
quickstart: {
@@ -34,6 +35,9 @@ interface SupportLevelMap {
[key: string]: QuickstartSupportLevel;
}
+// FIXME: We will want to clean this up and conform to the `Artifact` types
+// when we go to cleanup the deprecated mutation functionality after we have
+// finalized the new quickstart publishing pipeline.
const SUPPORT_LEVEL_ENUMS: SupportLevelMap = {
'New Relic': 'NEW_RELIC',
Community: 'COMMUNITY',
@@ -134,7 +138,11 @@ class Quickstart {
/**
* Get mutation variables from quickstart config
+ *
+ * @deprecated This function should be removed once we have finished our new build publishing pipeline
+ *
* @returns - Promised mutation variables for quickstart
+ *
*/
async getMutationVariables(
dryRun: boolean
@@ -198,6 +206,58 @@ class Quickstart {
};
}
+ /**
+ * Method extracts criteria from the config and returns an object appropriately
+ * structured for the artifact.
+ */
+ public transformForArtifact(): ArtifactQuickstartConfig {
+ const {
+ description,
+ title,
+ slug,
+ documentation,
+ icon,
+ keywords,
+ summary,
+ dataSourceIds,
+ id,
+ level,
+ authors = [],
+ dashboards = [],
+ alertPolicies = [],
+ } = this.config;
+
+ const metadata = {
+ quickstartUuid: id,
+ description: description && description.trim(),
+ displayName: title && title.trim(),
+ slug: slug && slug.trim(),
+ documentation:
+ documentation &&
+ documentation.map((doc) => ({
+ displayName: doc.name,
+ url: doc.url,
+ description: doc.description,
+ })),
+ iconUrl: this._constructIconUrl(icon),
+ keywords: keywords ?? [],
+ sourceUrl: Component.getAssetSourceUrl(
+ Component.removeBasePath(path.dirname(this.configPath), this.basePath)
+ ),
+ summary: summary && summary.trim(),
+ supportLevel: SUPPORT_LEVEL_ENUMS[
+ level
+ ]?.toLowerCase() as ArtifactQuickstartConfigSupportLevel,
+ dataSourceIds: dataSourceIds,
+ alertConditions: alertPolicies,
+ dashboards,
+ authors,
+ };
+
+
+ return metadata;
+ }
+
public async submitMutation(dryRun = true) {
logger.info(`Submitting mutation for ${this.identifier}`, { dryRun });
const { data, errors } = await fetchNRGraphqlResults<
diff --git a/utils/schema/artifact.json b/utils/schema/artifact.json
index 0114436fe4..fb9d5a9674 100644
--- a/utils/schema/artifact.json
+++ b/utils/schema/artifact.json
@@ -1,27 +1,40 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
-
"type": "object",
"properties": {
"quickstarts": {
"type": "array",
- "items": { "$ref": "#/definitions/quickstart" }
+ "items": {
+ "$ref": "#/definitions/quickstart"
+ }
},
"dataSources": {
"type": "array",
- "items": { "$ref": "#/definitions/dataSource" }
+ "items": {
+ "$ref": "#/definitions/dataSource"
+ }
},
"alerts": {
- "type": "array",
- "items": { "$ref": "#/definitions/alert" }
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "$ref": "#/definitions/alert"
+ }
+ }
},
"dashboards": {
- "type": "array",
- "items": { "$ref": "#/definitions/dashboard" }
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "$ref": "#/definitions/dashboard"
+ }
+ }
},
"dataSourceIds": {
"type": "array",
- "items": { "$ref": "#/definitions/dataSourceIds" }
+ "items": {
+ "$ref": "#/definitions/dataSourceIds"
+ }
}
},
"required": [
@@ -32,178 +45,576 @@
"dataSourceIds"
],
"additionalProperties": false,
-
"definitions": {
"quickstart": {
"type": "object",
"properties": {
- "id": { "type": "string" },
- "description": { "type": "string" },
- "summary": { "type": "string" },
- "title": { "type": "string" },
+ "quickstartUuid": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "summary": {
+ "type": "string"
+ },
+ "displayName": {
+ "type": "string"
+ },
"authors": {
"type": "array",
- "items": { "type": "string" }
+ "items": {
+ "type": "string"
+ }
+ },
+ "sourceUrl": {
+ "type": "string"
},
"documentation": {
"type": "array",
"items": {
"type": "object",
"properties": {
- "name": { "type": "string" },
- "description": { "type": "string" },
- "url": { "type": "string" }
+ "displayName": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ }
},
- "required": ["name", "description", "url"],
+ "required": ["displayName", "description", "url"],
"additionalProperties": false
}
},
- "level": { "enum": ["New Relic", "Community", "Verified"] },
- "icon": { "type": "string" },
+ "supportLevel": {
+ "enum": ["new_relic", "community", "verified", "enterprise"]
+ },
+ "iconUrl": {
+ "type": "string"
+ },
"keywords": {
"type": "array",
- "items": { "type": "string" }
+ "items": {
+ "type": "string"
+ }
},
- "slug": { "type": "string" },
- "alertPolicies": {
+ "slug": {
+ "type": "string"
+ },
+ "alertConditions": {
"type": "array",
- "items": { "type": "string" }
+ "items": {
+ "type": "string"
+ }
},
"dashboards": {
"type": "array",
- "items": { "type": "string" }
+ "items": {
+ "type": "string"
+ }
},
"dataSourceIds": {
"type": "array",
- "items": { "$ref": "#/definitions/dataSourceIds" }
+ "items": {
+ "$ref": "#/definitions/dataSourceIds"
+ }
}
},
"required": [
"description",
"summary",
- "title",
+ "displayName",
"authors",
- "icon"
+ "iconUrl"
],
"additionalProperties": false
},
-
"dataSource": {
"type": "object",
"properties": {
- "id": { "type": "string" },
- "displayName": { "type": "string" },
- "description": { "type": "string" },
+ "id": {
+ "type": "string"
+ },
+ "displayName": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
"install": {
"type": "object",
"properties": {
- "primary": { "$ref": "#/definitions/installDirective" },
- "fallback": { "$ref": "#/definitions/installDirective" }
+ "primary": {
+ "$ref": "#/definitions/installDirective"
+ },
+ "fallback": {
+ "$ref": "#/definitions/installDirective"
+ }
},
"required": ["primary"],
"additionalProperties": false
},
"keywords": {
- "type": "array", "items": { "type": "string" }
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
},
"categoryTerms": {
- "type": "array", "items": { "type": "string" }
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
},
- "icon": { "type": "string" }
+ "iconUrl": {
+ "type": "string"
+ }
},
"required": ["id", "displayName", "install"],
"additionalProperties": false
},
-
"installDirective": {
"type": "object",
"oneOf": [
{
"type": "object",
"properties": {
- "link": {
- "type": "object",
- "properties": {
- "url": { "type": "string" }
- },
- "required": ["url"],
- "additionalProperties": false
+ "url": {
+ "type": "string"
}
},
- "required": ["link"],
+ "required": ["url"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
- "nerdlet": {
- "type": "object",
- "properties": {
- "nerdletId": { "type": "string" },
- "nerdletState": { "type": "object" },
- "requiresAccount": { "type": "boolean" }
- },
- "required": ["nerdletId", "requiresAccount"],
- "additionalProperties": false
+ "nerdletId": {
+ "type": "string"
+ },
+ "nerdletState": {
+ "type": "object"
+ },
+ "requiresAccount": {
+ "type": "boolean"
}
},
- "required": ["nerdlet"],
+ "required": ["nerdletId", "requiresAccount"],
"additionalProperties": false
}
]
},
-
"alert": {
"type": "array",
"items": {
"type": "object",
"properties": {
- "name": { "type": "string" },
- "description": { "type": "string", "nullable": true },
- "type": { "enum": ["BASELINE", "STATIC"], "type": "string", "nullable": true },
- "nrql": {
- "type": "object",
- "properties": {
- "query": { "type": "string" }
- },
- "required": ["query"],
- "additionalProperties": true
+ "type": {
+ "enum": ["baseline", "static"],
+ "type": "string",
+ "nullable": true
},
- "runbookUrl": { "type": "string", "nullable": true },
- "violationTimeLimitSeconds": { "type": "number" },
- "enabled": { "type": "boolean" },
- "terms": {
+ "displayName": {
+ "type": "string"
+ },
+ "description": {
+ "type": ["string", "null"]
+ },
+ "rawConfiguration": {
+ "type": "string"
+ },
+ "sourceUrl": {
+ "type": "string"
+ },
+ "screenshots": {
"type": "array",
- "minItems": 1,
"items": {
"type": "object",
"properties": {
- "duration": { "type": "number", "minimum": 5, "maximum": 120 },
- "priority": { "enum": ["CRITICAL", "WARNING"] },
- "operator": { "enum": ["ABOVE", "BELOW", "EQUALS"] },
- "threshold": { "type": "number", "minimum": 0 },
- "thresholdDuration": { "type": "number", "minimum": 0 },
- "thresholdOccurances": { "enum": ["ALL", "AT_LEAST_ONCE"] }
+ "url": {
+ "type": "string"
+ }
},
- "additionalProperties": true
+ "required": ["url"]
}
}
},
- "required": ["name", "description", "type"],
- "additionalProperties": true
+ "required": ["displayName", "rawConfiguration"],
+ "additionalProperties": false
}
},
-
"dashboard": {
"type": "object",
"properties": {
- "name": { "type": "string" },
- "pages": { "type": "array", "minItems": 1 },
- "description": { "type": ["string", "null"] },
- "variables": { "type": "array" }
+ "displayName": {
+ "type": "string"
+ },
+ "description": {
+ "type": ["string", "null"]
+ },
+ "rawConfiguration": {
+ "type": "string"
+ },
+ "sourceUrl": {
+ "type": "string"
+ },
+ "screenshots": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ }
+ },
+ "required": ["url"]
+ }
+ }
},
- "required": ["name", "pages"],
+ "required": ["displayName", "rawConfiguration"],
"additionalProperties": false
+ },
+ "dataSourceIds": {
+ "enum": [
+ "active-directory",
+ "ads-web-gpt",
+ "ads-web-prebid",
+ "aerospike",
+ "akamai",
+ "alert-quality-management",
+ "amazon-cloudwatch-metric-streams",
+ "amazon-eks-on-aws-fargate",
+ "ansible-automation-controller",
+ "apache-druid",
+ "apache-flink",
+ "apache-hadoop",
+ "apache-mesos",
+ "apache-traffic-server",
+ "apache-zookeeper",
+ "apdex-optimizer",
+ "apigee-api",
+ "apm-signals",
+ "argocd",
+ "atlassian-jira",
+ "audit-events-analysis",
+ "aws-cloudfront-logs",
+ "azure-monitor",
+ "azure-virtual-machines",
+ "battlesnake",
+ "bitbucket",
+ "third-party-biztalk360",
+ "blameless",
+ "blazemeter",
+ "blazor-webassembly",
+ "boomi",
+ "browser-segment-investigation",
+ "browser-signals",
+ "cacti",
+ "camel",
+ "catchpoint-quickstart",
+ "circleci",
+ "cloud-spanner",
+ "cloudflare",
+ "contentsquare",
+ "conviva",
+ "cribl",
+ "customer-experience-bottom-funnel-analysis",
+ "customer-experience-quality-foundation",
+ "dapr",
+ "databricks",
+ "datazoom",
+ "dbmarlin",
+ "dbt-cloud",
+ "deeper-network",
+ "delphix",
+ "docker-otel",
+ "dojo",
+ "elasticsearch-query",
+ "elixir",
+ "email-notifications",
+ "envoy",
+ "event-api",
+ "fastly",
+ "fivetran",
+ "flutter-android",
+ "flutter-ios",
+ "flutter-web",
+ "full-story",
+ "gatsby-build-newrelic",
+ "gcp-pubsub",
+ "gh-gates",
+ "gigamon-appinsights",
+ "github-repo",
+ "gitlab-integration",
+ "glassbox",
+ "grafana-dashboard-migration",
+ "grafana-prometheus-integration",
+ "gridgain-data-source",
+ "hardware-sentry",
+ "hcp-consul-otel",
+ "hcp-consul",
+ "hcp-envoy",
+ "hcp-vault-logs",
+ "hcp-vault-metrics",
+ "hivemq-integration-docs",
+ "ibmmq-integration-docs",
+ "infrastructure-signals",
+ "jdbc-executebatch",
+ "otel-jenkins-integration",
+ "jfrog-platform-cloud",
+ "jfrog-platform",
+ "jira-errors-inbox",
+ "k6-prometheus",
+ "k6",
+ "kentik",
+ "lacework",
+ "lambdatest",
+ "langchain-vectordb",
+ "langchain",
+ "launchdarkly",
+ "lighthouse",
+ "lighttpd",
+ "linkerd",
+ "logs-api",
+ "logs-default",
+ "metric-api",
+ "mobile-getting-started",
+ "mobile-network-performance-install",
+ "mobile-signals",
+ "mongodb-prometheus-integration-docs",
+ "mule-esb",
+ "mux-video",
+ "netlify-builds",
+ "netlify-logs",
+ "netscaler",
+ "network-data-ingest-and-cardinality",
+ "network-routers-and-switches",
+ "network-syslog",
+ "newrelic-cli",
+ "nextcloud",
+ "nr-reports",
+ "nuxtjs",
+ "nvidia-dcgm",
+ "nvidia-gpu",
+ "nvidia-jetson",
+ "nvidia-triton",
+ "nvml",
+ "ocsf",
+ "okhttp",
+ "onepane",
+ "opencensus",
+ "openstack-controller",
+ "opslevel",
+ "pagerduty",
+ "perfmon",
+ "pihole",
+ "port-monitoring",
+ "postfix",
+ "postman",
+ "pulumi",
+ "quantum-metric",
+ "rafay",
+ "ray",
+ "redis-enterprise",
+ "redis-otel",
+ "redmine",
+ "redpanda",
+ "releaseiq",
+ "newrelic-java-rmi",
+ "roku-http-analytics",
+ "roku",
+ "salesforce-eventlog-for-logs",
+ "sendgrid-integration",
+ "sendmail",
+ "servicenow-notifications",
+ "shopify-hydrogen",
+ "shopify",
+ "signl4",
+ "singlestore",
+ "skykit",
+ "slack-notifications",
+ "snowflake",
+ "solutions-hub-dashboards",
+ "sonarqube",
+ "speedscale",
+ "split",
+ "squid-prometheus-integration-docs",
+ "stripe",
+ "sybase-integration",
+ "synthetics-private-locations",
+ "telemetry-data-platform",
+ "temporal-cloud",
+ "temporal",
+ "terraform",
+ "tidbcloud",
+ "trace-and-span-api",
+ "traceloop",
+ "trendmicro-cloudone-conformity",
+ "tvos-mobile",
+ "unix",
+ "vercel",
+ "vertica",
+ "newrelic-java-vertx",
+ "newrelic-java-vertx-extensions",
+ "video-android",
+ "video-chromecast",
+ "video-ios-tvos",
+ "video-web-akamai",
+ "video-web-html5",
+ "video-web-jwplayer",
+ "video-web-theplatform",
+ "video-web-videojs",
+ "webhook-notifications",
+ "windows-certs",
+ "winservices-integration-docs",
+ "xmatters",
+ "zebrium",
+ "zenduty",
+ "zipkin",
+ "activemq",
+ "amazon-cloudwatch",
+ "amazon-kinesis",
+ "amazon-linux",
+ "amazon-linux-logs",
+ "amazon-s3",
+ "amazon-sagemaker",
+ "amazon-web-services",
+ "android",
+ "angular",
+ "angular-js",
+ "ansible-install",
+ "apache",
+ "apm-logs",
+ "aporia",
+ "aws-firelens",
+ "aws-lambda",
+ "aws-migrate-metricStream",
+ "aws-security-hub",
+ "aws-vpc-flow-logs",
+ "azure-openai",
+ "backbone",
+ "bring-your-own-data",
+ "capacitor",
+ "cassandra",
+ "centos",
+ "centos-logs",
+ "change-tracking",
+ "chef-install",
+ "comet",
+ "confluent-cloud",
+ "consul",
+ "cordova",
+ "couchbase",
+ "dagshub",
+ "debian",
+ "debian-logs",
+ "dependabot",
+ "distributed-tracing",
+ "docker",
+ "dropwizard",
+ "elasticsearch",
+ "ember",
+ "f5",
+ "fluent-bit",
+ "fluentd",
+ "flutter",
+ "gatsby",
+ "gcp-vpc-flow-logs",
+ "golang",
+ "google-cloud-platform",
+ "google-cloud-platform-logs",
+ "govcloud",
+ "guided-install",
+ "haproxy",
+ "headerless-log-api",
+ "heroku",
+ "heroku-http",
+ "ios",
+ "java",
+ "java-logs",
+ "jmx",
+ "jquery",
+ "kafka",
+ "kamon",
+ "kubernetes",
+ "kubernetes-logs",
+ "linux",
+ "linux-infra",
+ "linux-logs",
+ "llm-application",
+ "logstash",
+ "macos",
+ "maui",
+ "memcached",
+ "micrometer",
+ "microsoft-azure",
+ "microsoft-azure-blob-storage",
+ "microsoft-azure-event-hub",
+ "microsoft-sql-server",
+ "microsoftnet",
+ "microsoftnet-logs",
+ "mona",
+ "mongodb",
+ "mysql",
+ "nagios",
+ "network-devices",
+ "network-flows",
+ "network-synthetics",
+ "new-relic-browser",
+ "new-relic-infrastructure-agent",
+ "new-relic-synthetics",
+ "next-js",
+ "nginx",
+ "node-js",
+ "nr-ai-monitoring",
+ "nvidia-nim",
+ "opentelemetry",
+ "oracle-database",
+ "php",
+ "pixie",
+ "postgresql",
+ "powerdns",
+ "prometheus",
+ "prometheus-agent-mode",
+ "puppet-install",
+ "python",
+ "rabbitmq",
+ "react",
+ "react-native",
+ "redis",
+ "rhel",
+ "rhel-logs",
+ "ruby",
+ "ruby-logs",
+ "security-api",
+ "sles",
+ "sles-logs",
+ "snmp",
+ "snyk",
+ "statsd",
+ "super-agent",
+ "syslog",
+ "trivy",
+ "truera",
+ "ubuntu",
+ "ubuntu-logs",
+ "unity",
+ "unreal",
+ "varnish",
+ "vmware-tanzu",
+ "vmware-vsphere",
+ "vue",
+ "windows",
+ "windows-infra",
+ "windows-logs",
+ "xamarin",
+ "xcframework",
+ "zepto"
+ ]
}
}
}
+
diff --git a/utils/types/Artifact.ts b/utils/types/Artifact.ts
new file mode 100644
index 0000000000..88fd0660d7
--- /dev/null
+++ b/utils/types/Artifact.ts
@@ -0,0 +1,100 @@
+/* -- Data source -- */
+
+export interface ArtifactDataSourceConfig {
+ id: string;
+ displayName: string;
+ description?: string;
+ install: ArtifactInstallDirective;
+ keywords?: string[];
+ categoryTerms?: string[];
+ iconUrl?: string;
+}
+
+export type ArtifactInstallDirective = {
+ primary: ArtifactInstall;
+ fallback?: ArtifactInstall;
+};
+
+export type ArtifactInstall =
+ | ArtifactDataSourceConfigNerdletDirective
+ | ArtifactDataSourceConfigLinkDirective;
+
+interface ArtifactDataSourceConfigNerdletDirective {
+ nerdletId: string;
+ nerdletState: Record;
+ requiresAccount: boolean;
+}
+
+interface ArtifactDataSourceConfigLinkDirective {
+ url: string;
+}
+
+interface ArtifactDataSourceConfigNerdletDirective {
+ nerdletId: string;
+ nerdletState: Record;
+ requiresAccount: boolean;
+}
+
+/* -- Quickstart -- */
+
+type QuickstartConfigDocumentation = {
+ displayName: string;
+ description: string;
+ url: string;
+};
+
+export type ArtifactQuickstartConfigSupportLevel =
+ | 'new_relic'
+ | 'community'
+ | 'verified'
+ // Enterprise is deprecated. However some quickstarts still have this support
+ // level within their config.
+ | 'enterprise';
+
+export interface ArtifactQuickstartConfig {
+ quickstartUuid: string;
+ description: string;
+ displayName: string;
+ slug?: string;
+ documentation: QuickstartConfigDocumentation[];
+ iconUrl: string;
+ keywords?: string[];
+ summary: string;
+ supportLevel: ArtifactQuickstartConfigSupportLevel;
+ alertConditions?: string[];
+ dashboards?: string[];
+ dataSourceIds?: string[];
+ authors: string[];
+};
+
+/* -- Dashboard -- */
+
+type DashboardScreenshot = {
+ url: string;
+};
+
+export interface ArtifactDashboardConfig {
+ [id: string]: {
+ description?: string;
+ displayName: string;
+ rawConfiguration: string;
+ sourceUrl?: string;
+ screenshots?: DashboardScreenshot[];
+ };
+}
+
+/* --- Alert --- */
+
+export type ArtifactAlertType = 'baseline' | 'static';
+
+type ArtifactAlert = {
+ description?: string;
+ displayName: string;
+ rawConfiguration: string;
+ sourceUrl?: string;
+ type: ArtifactAlertType;
+};
+
+export interface ArtifactAlertConfig {
+ [id: string]: ArtifactAlert[];
+}