Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/api_reference/testmonitor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ nisystemlink.clients.testmonitor
:exclude-members: __init__

.. automethod:: __init__
.. automethod:: create_results
.. automethod:: get_results
.. automethod:: query_results
.. automethod:: query_result_values
.. automethod:: update_results
.. automethod:: delete_result
.. automethod:: delete_results

.. automodule:: nisystemlink.clients.testmonitor.models
:members:
Expand Down
26 changes: 26 additions & 0 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,29 @@ Delete a feed.
.. literalinclude:: ../examples/feeds/delete_feed.py
:language: python
:linenos:

TestMonitor API (Results)
-------

Overview
~~~~~~~~

The :class:`.TestMonitorClient` class is the primary entry point of the Results API.

When constructing a :class:`.TestMonitorClient`, you can pass an
:class:`.HttpConfiguration` (like one retrieved from the
:class:`.HttpConfigurationManager`), or let :class:`.TestMonitorClient` use the
default connection. The default connection depends on your environment.

With a :class:`.TestMonitorClient` object, you can:

* Create, update, query, and delete results

Examples
~~~~~~~~

Create, query, update, and delete some results

.. literalinclude:: ../examples/result/results.py
:language: python
:linenos:
90 changes: 90 additions & 0 deletions examples/testmonitor/results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from nisystemlink.clients.testmonitor import TestMonitorClient
from nisystemlink.clients.testmonitor.models import (
CreateResultRequest,
QueryResultsRequest,
QueryResultValuesRequest,
ResultField,
ResultStatus,
StandardResultStatus,
StatusType,
)

program_name = "Example Name"
host_name = "Example Host"
status_type = StatusType.PASSED


def create_some_results():
"""Create two example results on your server."""
new_results = [
CreateResultRequest(
part_number="Example 123 AA",
program_name=program_name,
host_name=host_name,
status=StandardResultStatus.PASSED,
keywords=["original keyword"],
properties={"original property key": "yes"},
),
CreateResultRequest(
part_number="Example 123 AA1",
program_name=program_name,
host_name=host_name,
status=ResultStatus(status_type=StatusType.CUSTOM, status_name="Custom"),
keywords=["original keyword"],
properties={"original property key": "original"},
),
]
create_response = client.create_results(new_results)
return create_response


# Server configuration is not required when used with Systemlink Client or run throught Jupyter on SLE
server_configuration = None

# # Example of setting up the server configuration to point to your instance of SystemLink Enterprise
# server_configuration = HttpConfiguration(
# server_uri="https://yourserver.yourcompany.com",
# api_key="YourAPIKeyGeneratedFromSystemLink",
# )

client = TestMonitorClient(configuration=server_configuration)

create_response = create_some_results()

# Get all the results using the continuation token in batches of 100 at a time.
response = client.get_results(take=100, return_count=True)
all_results = response.results
while response.continuation_token:
response = client.get_results(
take=100, continuation_token=response.continuation_token, return_count=True
)
all_results.extend(response.results)

# use get for first result created
created_result = client.get_result(create_response.results[0].id)

# Query results without continuation
query_request = QueryResultsRequest(
filter=f'status.statusType="{status_type}"', return_count=True
)
response = client.query_results(query_request)

# Update the first result that you just created and replace the keywords
updated_result = create_response.results[0]
updated_result.keywords = ["new keyword"]
updated_result.properties = {"new property key": "new value"}
update_response = client.update_results([create_response.results[0]], replace=True)

# Query for just the ids of results that match the family
values_query = QueryResultValuesRequest(
filter=f'programName="{program_name}"', field=ResultField.ID
)
values_response = client.query_result_values(query=values_query)

# delete each created result individually by id
for result in create_response.results:
client.delete_result(result.id)

# Create some more and delete them with a single call to delete.
create_response = create_some_results()
client.delete_results([result.id for result in create_response.results])
161 changes: 159 additions & 2 deletions nisystemlink/clients/testmonitor/_test_monitor_client.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
"""Implementation of TestMonitor Client"""

from typing import Optional
from typing import List, Optional

from nisystemlink.clients import core
from nisystemlink.clients.core._uplink._base_client import BaseClient
from nisystemlink.clients.core._uplink._methods import get
from nisystemlink.clients.core._uplink._methods import delete, get, post
from nisystemlink.clients.testmonitor.models import (
CreateResultRequest,
UpdateResultRequest,
)
from uplink import Field, Query, retry, returns

from . import models


@retry(
when=retry.when.status([408, 429, 502, 503, 504]), stop=retry.stop.after_attempt(5)
)
class TestMonitorClient(BaseClient):
# prevent pytest from thinking this is a test class
__test__ = False
Expand Down Expand Up @@ -40,3 +48,152 @@ def api_info(self) -> models.ApiInfo:
ApiException: if unable to communicate with the `ni``/nitestmonitor``` service.
"""
...

@post("results", args=[Field("results")])
def create_results(
self, results: List[CreateResultRequest]
) -> models.CreateResultsPartialSuccess:
"""Creates one or more results and returns errors for failed creations.

Args:
results: A list of results to attempt to create.

Returns: A list of created results, results that failed to create, and errors for
failures.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` service of provided invalid
arguments.
"""
...

@get(
"results",
args=[Query("continuationToken"), Query("take"), Query("returnCount")],
)
def get_results(
self,
continuation_token: Optional[str] = None,
take: Optional[int] = None,
return_count: Optional[bool] = None,
) -> models.PagedResults:
"""Reads a list of results.

Args:
continuation_token: The token used to paginate results.
take: The number of results to get in this request.
return_count: Whether or not to return the total number of results available.

Returns:
A list of results.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...

@get("results/{id}")
def get_result(self, id: str) -> models.Result:
"""Retrieves a single result by id.

Args:
id (str): Unique ID of a result.

Returns:
The single result matching `id`

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...

@post("query-results")
def query_results(self, query: models.QueryResultsRequest) -> models.PagedResults:
"""Queries for results that match the filter.

Args:
query : The query contains a DynamicLINQ query string in addition to other details
about how to filter and return the list of results.

Returns:
A paged list of results with a continuation token to get the next page.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service or provided invalid
arguments.
"""
...

@returns.json # type: ignore
@post("query-result-values")
def query_result_values(self, query: models.QueryResultValuesRequest) -> List[str]:
"""Queries for results that match the query and returns a list of the requested field.

Args:
query : The query for the fields.

Returns:
A list of the values of the queried field.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service or provided
invalid arguments.
"""
...

@post("update-results", args=[Field("results"), Field("replace")])
def update_results(
self, results: List[UpdateResultRequest], replace: bool = False
) -> models.UpdateResultsPartialSuccess:
"""Updates a list of results with optional field replacement.

Args:
`results`: A list of results to update. Results are matched for update by id.
`replace`: Replace the existing fields instead of merging them. Defaults to `False`.
If this is `True`, then `keywords` and `properties` for the result will be
replaced by what is in the `results` provided in this request.
If this is `False`, then the `keywords` and `properties` in this request will
merge with what is already present in the server resource.

Returns: A list of updates results, results that failed to update, and errors for
failures.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...

@delete("results/{id}")
def delete_result(self, id: str) -> None:
"""Deletes a single result by id.

Args:
id (str): Unique ID of a result.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...

@post("delete-results", args=[Field("ids")])
def delete_results(
self, ids: List[str]
) -> Optional[models.DeleteResultsPartialSuccess]:
"""Deletes multiple results.

Args:
ids (List[str]): List of unique IDs of results.

Returns:
A partial success if any results failed to delete, or None if all
results were deleted successfully.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...
12 changes: 12 additions & 0 deletions nisystemlink/clients/testmonitor/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
from ._api_info import Operation, V2Operations, ApiInfo
from ._result import Result, ResultStatus, StatusType, StandardResultStatus
from ._create_results_partial_success import CreateResultsPartialSuccess
from ._update_results_partial_success import UpdateResultsPartialSuccess
from ._delete_results_partial_success import DeleteResultsPartialSuccess
from ._paged_results import PagedResults
from ._create_result_request import CreateResultRequest
from ._update_result_request import UpdateResultRequest
from ._query_results_request import (
QueryResultsRequest,
ResultField,
QueryResultValuesRequest,
)

# flake8: noqa
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from datetime import datetime
from typing import Dict, List, Optional

from nisystemlink.clients.core._uplink._json_model import JsonModel
from nisystemlink.clients.testmonitor.models._result import ResultStatus


class CreateResultRequest(JsonModel):
"""Contains information about a result."""

status: ResultStatus
"""The status of the result."""

started_at: Optional[datetime]
"""The time that the result started."""

program_name: str
"""The name of the program that generated this result."""

system_id: Optional[str]
"""The id of the system that generated this result."""

host_name: Optional[str]
"""The name of the host that generated this result."""

part_number: Optional[str]
"""The part number is the unique identifier of a product within a single org."""

serial_number: Optional[str]
"""The serial number of the system that generated this result."""

total_time_in_seconds: Optional[float]
"""The total time that the result took to run in seconds."""

keywords: Optional[List[str]]
"""A list of keywords that categorize this result."""

properties: Optional[Dict[str, str]]
"""A list of custom properties for this result."""

operator: Optional[str]
"""The operator that ran the result."""

file_ids: Optional[List[str]]
"""A list of file ids that are attached to this result."""

data_table_ids: Optional[List[str]]
"""A list of data table ids that are attached to this result."""

workspace: Optional[str]
"""The id of the workspace that this product belongs to."""
Loading