Skip to content

Commit b4cc95c

Browse files
author
Jonathan Visser
committed
add bin/create_insights_annotation
1 parent a3369c3 commit b4cc95c

5 files changed

Lines changed: 200 additions & 0 deletions

File tree

bin/create_insights_annotation

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
command

hypernode_api_python/client.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
HYPERNODE_API_FPM_STATUS_APP_ENDPOINT = "/v2/nats/{}/hypernode.show-fpm-status"
2525
HYPERNODE_API_BRANCHER_ENDPOINT = "/v2/brancher/{}/"
2626
HYPERNODE_API_INSIGHTS_ANNOTATION_LIST_ENDPOINT = "/v2/insights-annotation/"
27+
HYPERNODE_API_INSIGHTS_ANNOTATION_CREATE_ENDPOINT = "/v2/insights-annotation/create/"
2728
HYPERNODE_API_PRODUCT_APP_DETAIL_ENDPOINT = "/v2/product/app/{}/current/"
2829
HYPERNODE_API_PRODUCT_LIST_ENDPOINT = "/v2/product/"
2930
HYPERNODE_API_PRODUCT_PRICE_DETAIL_ENDPOINT = "/v2/product/{}/with_price/"
@@ -958,6 +959,41 @@ def get_insights_annotations(self, params=None):
958959
"GET", HYPERNODE_API_INSIGHTS_ANNOTATION_LIST_ENDPOINT, params=params
959960
)
960961

962+
def create_insights_annotation(self, data):
963+
"""
964+
Create a custom Insights annotation for a Hypernode. The annotation
965+
is shown in the Insights graphs at the point in time given by
966+
'x_axis' (a unix timestamp in seconds). Optionally restrict the
967+
annotation to specific graphs with 'metrics', a comma-separated list
968+
of metric names. When omitted, the annotation applies to all metrics.
969+
Example:
970+
> client.create_insights_annotation(
971+
> data={
972+
> 'name': 'Deployed release 1.2.3',
973+
> 'x_axis': 1756384957,
974+
> 'app': 'yourhypernodeappname',
975+
> 'metrics': 'memory_usage'
976+
> }
977+
> ).json()
978+
> {
979+
> 'id': 123,
980+
> 'name': 'Deployed release 1.2.3',
981+
> 'x_axis': 1756384957,
982+
> 'app': 'yourhypernodeappname',
983+
> 'metrics': 'memory_usage',
984+
> 'created': '2025-08-28T12:42:37Z',
985+
> 'modified': '2025-08-28T12:42:37Z'
986+
> }
987+
988+
:param dict data: Data describing the Insights annotation to create.
989+
An example could be: {'name': 'Deployed release 1.2.3',
990+
'x_axis': 1756384957, 'app': 'yourhypernodeappname'}
991+
:return obj response: The request response object
992+
"""
993+
return self.requests(
994+
"POST", HYPERNODE_API_INSIGHTS_ANNOTATION_CREATE_ENDPOINT, data=data
995+
)
996+
961997
def create_brancher(self, app_name, data):
962998
"""
963999
Create a new branch (server replica) of your Hypernode.

hypernode_api_python/commands.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,57 @@ def get_insights_annotations(args=None):
847847
print_response(client.get_insights_annotations(params=params))
848848

849849

850+
def create_insights_annotation(args=None):
851+
parser = ArgumentParser(
852+
description="""
853+
Create a custom Insights annotation for the Hypernode. The annotation is
854+
shown in the Insights graphs at the specified point in time. Optionally
855+
restrict the annotation to specific graphs with --metrics. When omitted,
856+
the annotation applies to all metrics.
857+
858+
Example:
859+
$ ./bin/create_insights_annotation "Deployed release 1.2.3" 1756384957 --metrics memory_usage
860+
{
861+
"id": 123,
862+
"name": "Deployed release 1.2.3",
863+
"x_axis": 1756384957,
864+
"app": "yourhypernodeappname",
865+
"metrics": "memory_usage",
866+
"created": "2025-08-28T12:42:37Z",
867+
"modified": "2025-08-28T12:42:37Z"
868+
}
869+
""",
870+
formatter_class=RawTextHelpFormatter,
871+
)
872+
parser.add_argument(
873+
"name",
874+
help="The name of the annotation to create",
875+
)
876+
parser.add_argument(
877+
"x_axis",
878+
help="The point in time of the annotation as a unix timestamp in seconds",
879+
type=int,
880+
)
881+
parser.add_argument(
882+
"--metrics",
883+
help="An optional comma-separated list of metric names to restrict "
884+
"the annotation to",
885+
)
886+
parser.add_argument(
887+
"--metadata",
888+
help="Optional metadata to store with the annotation",
889+
)
890+
args = parser.parse_args(args=args)
891+
client = get_client()
892+
app_name = get_app_name()
893+
data = {"name": args.name, "x_axis": args.x_axis, "app": app_name}
894+
if args.metrics is not None:
895+
data["metrics"] = args.metrics
896+
if args.metadata is not None:
897+
data["metadata"] = args.metadata
898+
print_response(client.create_insights_annotation(data=data))
899+
900+
850901
def get_fpm_status(args=None):
851902
parser = ArgumentParser(
852903
description="""
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from unittest import TestCase
2+
from unittest.mock import Mock
3+
4+
from hypernode_api_python.client import (
5+
HYPERNODE_API_INSIGHTS_ANNOTATION_CREATE_ENDPOINT,
6+
HypernodeAPIPython,
7+
)
8+
9+
10+
class TestCreateInsightsAnnotation(TestCase):
11+
def setUp(self):
12+
self.client = HypernodeAPIPython(token="my_token")
13+
self.mock_request = Mock()
14+
self.client.requests = self.mock_request
15+
16+
def test_insights_annotation_create_endpoint_is_correct(self):
17+
self.assertEqual(
18+
"/v2/insights-annotation/create/",
19+
HYPERNODE_API_INSIGHTS_ANNOTATION_CREATE_ENDPOINT,
20+
)
21+
22+
def test_calls_create_insights_annotation_endpoint_properly(self):
23+
data = {
24+
"name": "Deployed release 1.2.3",
25+
"x_axis": 1756384957,
26+
"app": "my_app",
27+
"metrics": "memory_usage",
28+
}
29+
self.client.create_insights_annotation(data)
30+
31+
self.mock_request.assert_called_once_with(
32+
"POST", "/v2/insights-annotation/create/", data=data
33+
)
34+
35+
def test_returns_create_insights_annotation_response(self):
36+
response = Mock()
37+
response.status_code = 201
38+
self.mock_request.return_value = response
39+
40+
self.assertEqual(
41+
self.client.create_insights_annotation({}),
42+
self.mock_request.return_value,
43+
)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from hypernode_api_python.commands import create_insights_annotation
2+
from tests.testcase import TestCase
3+
4+
5+
class TestCreateInsightsAnnotation(TestCase):
6+
def setUp(self):
7+
self.print_response = self.set_up_patch(
8+
"hypernode_api_python.commands.print_response"
9+
)
10+
self.get_client = self.set_up_patch("hypernode_api_python.commands.get_client")
11+
self.client = self.get_client.return_value
12+
self.get_app_name = self.set_up_patch(
13+
"hypernode_api_python.commands.get_app_name"
14+
)
15+
self.get_app_name.return_value = "myappname"
16+
17+
def test_create_insights_annotation_gets_client(self):
18+
create_insights_annotation(["Deployed release 1.2.3", "1756384957"])
19+
20+
self.get_client.assert_called_once_with()
21+
22+
def test_create_insights_annotation_gets_app_name(self):
23+
create_insights_annotation(["Deployed release 1.2.3", "1756384957"])
24+
25+
self.get_app_name.assert_called_once_with()
26+
27+
def test_create_insights_annotation_creates_annotation(self):
28+
create_insights_annotation(["Deployed release 1.2.3", "1756384957"])
29+
30+
expected_data = {
31+
"name": "Deployed release 1.2.3",
32+
"x_axis": 1756384957,
33+
"app": "myappname",
34+
}
35+
self.client.create_insights_annotation.assert_called_once_with(
36+
data=expected_data
37+
)
38+
39+
def test_create_insights_annotation_creates_annotation_with_optional_arguments(
40+
self,
41+
):
42+
create_insights_annotation(
43+
[
44+
"Deployed release 1.2.3",
45+
"1756384957",
46+
"--metrics",
47+
"memory_usage",
48+
"--metadata",
49+
"some metadata",
50+
]
51+
)
52+
53+
expected_data = {
54+
"name": "Deployed release 1.2.3",
55+
"x_axis": 1756384957,
56+
"app": "myappname",
57+
"metrics": "memory_usage",
58+
"metadata": "some metadata",
59+
}
60+
self.client.create_insights_annotation.assert_called_once_with(
61+
data=expected_data
62+
)
63+
64+
def test_create_insights_annotation_prints_response(self):
65+
create_insights_annotation(["Deployed release 1.2.3", "1756384957"])
66+
67+
self.print_response.assert_called_once_with(
68+
self.client.create_insights_annotation.return_value
69+
)

0 commit comments

Comments
 (0)