Details
| ID | Aha |
|---|---|
| Provider | Aha Labs Inc. |
| Category | Utilities |
| From Version | 6.5.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Use the Aha! integration to list and manage Cortex XSOAR features from Aha.
This integration was integrated and tested with API version December 02, 2022 release of Aha.
Configure Aha in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | True | |
| Project Name | Check the Aha! project name in the URL. Replace the <PROJECT_NAME> placeholder in the following : example.com.aha.io/products/<PROJECT_NAME>/features. | True |
| Api Key | API Key to access the service REST API. | True |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
aha-get-features
Lists all features from service, unless a specific feature is specified.
Base Command
aha-get-features
Input
| Argument Name | Description | Required |
|---|---|---|
| from_date | Show features created after this date. Default is 2020-01-01. | Optional |
| feature_name | The name of a specific feature to retrieve. | Optional |
| fields | A comma-separated list of fields to include in the Aha! service response. Default is name,reference_num,id,created_at. | Optional |
| page | The specific results page to retrieve. Default is 1. | Optional |
| per_page | The maximum number of results per page. Default is 30. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AHA.Feature.id | UUID | The feature ID. |
| AHA.Feature.name | String | The feature name. |
| AHA.Feature.reference_num | String | The feature reference number. |
| AHA.Feature.workflow_status | String | The feature status description. |
| AHA.Feature.description | String | The feature description. |
| AHA.Feature.created_at | Date | The feature creation date. |
Command example
!aha-get-features
!aha-get-features feature_name=DEMO-10 fields=workflow_status
!aha-get-features fields=workflow_status page=2 per_page=30
aha-edit-feature
You can edit the following fields in a feature: Name and Description.
Base Command
aha-edit-feature
Input
| Argument Name | Description | Required |
|---|---|---|
| feature_name | The name of the feature to edit. | Required |
| fields | Fields in JSON format to edit in a feature. Possible fields are name and status. Status should match Aha values under workflow_status. Example:” {“name”: “name”, “status” : “Closed”}. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AHA.Feature.id | UUID | The feature ID. |
| AHA.Feature.name | String | The feature name. |
| AHA.Feature.reference_num | String | The feature reference number. |
| AHA.Feature.workflow_status | String | The feature status description. |
| AHA.Feature.description | String | The feature description. |
| AHA.Feature.created_at | Date | The feature creation date. |
Command example
!aha-edit-feature feature_name=DEMO-10 fields=`{"name":"the_new_name", "status":"Closed"}
aha-get-ideas
Lists all ideas from service, unless a specific idea is specified.
Base Command
aha-get-ideas
Input
| Argument Name | Description | Required |
|---|---|---|
| from_date | Show ideas created after this date. Default is 2020-01-01. | Optional |
| idea_name | The name of a specific idea to retrieve. | Optional |
| fields | A comma-separated list of fields to include in the Aha! service response. Default is name,reference_num,id,created_at. | Optional |
| page | The specific results page to retrieve. Default is 1. | Optional |
| per_page | The maximum number of results per page. Default is 30. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AHA.Idea.id | UUID | The idea ID. |
| AHA.Idea.name | String | The idea name. |
| AHA.Idea.reference_num | String | The idea reference number. |
| AHA.Idea.workflow_status | String | The idea status description. |
| AHA.Idea.description | String | The idea description. |
| AHA.Idea.created_at | Date | The idea creation date. |
Command example
!aha-get-ideas
!aha-get-ideas idea_name=DEMO-I-2895
!aha-get-ideas idea_name=DEMO-I-2895 fields=workflow_status
aha-edit-idea
Edit an idea status to Shipped.
Base Command
aha-edit-idea
Input
| Argument Name | Description | Required |
|---|---|---|
| idea_name | The name of the idea to edit. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AHA.Idea.id | UUID | The idea ID. |
| AHA.Idea.name | String | The idea name. |
| AHA.Idea.reference_num | String | The idea reference number. |
| AHA.Idea.workflow_status | String | The idea status description. |
| AHA.Idea.description | String | The idea description. |
| AHA.Idea.created_at | Date | The idea creation date. |
Command example
!aha-edit-idea idea_name=DEMO-I-2895
Configuration parameters
url— Server URL (required)project_name— Project Name (required)api_key— (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (4)
-
aha-edit-featureYou can edit the following fields in a feature: Name and Status.
-
aha-edit-ideaEdit an idea status to Shipped.
-
aha-get-featuresLists all features from service, unless a specific feature is specified.
-
aha-get-ideasLists all ideas from service, unless a specific idea is specified.
import demistomock as demisto # noqa: F401 import pytest from AHA import AHA_TYPE, Client, edit_command, get_command from CommonServerPython import * # noqa: F401 headers = { "Authorization": "Bearer test_key", } BASE_URL = "https://example.com.aha.io/api/v1/" def mock_client(mocker, http_request_result=None, throw_error=False): mocker.patch.object(demisto, "getIntegrationContext", return_value={"current_refresh_token": "refresh_token"}) client = Client(headers=headers, base_url=BASE_URL, proxy=False, verify=False, url="DEMO") if http_request_result: mocker.patch.object(client, "_http_request", return_value=http_request_result) if throw_error: err_msg = "Error in API call [400] - BAD REQUEST}" mocker.patch.object(client, "_http_request", side_effect=DemistoException(err_msg, res={})) return client def util_load_json(path): with open(path, encoding="utf-8") as f: return json.loads(f.read()) def test_main(mocker): """ Given: - All return values from helper functions are valid When: - main function test-module is executed Then: - Return ok result to War-Room """ from AHA import main mocker.patch.object( demisto, "params", return_value={ "url": "example.com", "project_name": "DEMO", "api_key": {"password": "test_api"}, }, ) mocker.patch("AHA.Client.get", return_value={"name": "test"}) mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "results") main() assert demisto.results.call_count == 1 assert demisto.results.call_args[0][0] == "ok" def test_notImplementedCommand(mocker): """ Given: - All return values from helper functions are valid When: - Calling main function with invalid command Then: - Return sys.exit(0) """ from AHA import main mocker.patch.object( demisto, "params", return_value={ "url": "example.com", "project_name": "DEMO", "api_key": {"password": "test_api"}, }, ) mocker.patch("AHA.Client.get", return_value={"name": "test"}) mocker.patch.object(demisto, "command", return_value="tests-module") mocker.patch.object(demisto, "results") with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type is SystemExit assert pytest_wrapped_e.value.code == 0 def test_Module(mocker): """ Given: - client is properly configured When: - calling test-module Then: - Return ok result """ from AHA import test_module client = mock_client(mocker, util_load_json("test_data/get_all_features.json")) results = test_module(client=client) assert results == "ok" def test_getFeatures(mocker): """ When: - Requesting all features Then: - Asserts get a list of expected length with all features. """ client = mock_client(mocker, util_load_json("test_data/get_all_features.json")) results = get_command(client=client, aha_type=AHA_TYPE.FEATURES, from_date="2022-01-01") assert len(results.outputs) == 3 assert len(results.outputs[0].get("ideas")) == 1 assert results.outputs[0].get("ideas")[0] == "DEMO-I-299" def test_getIdeas(mocker): """ When: - Requesting all ideas Then: - Asserts get a list of expected length with all ideas. """ client = mock_client(mocker, util_load_json("test_data/get_all_ideas.json")) results = get_command(client=client, aha_type=AHA_TYPE.IDEAS, from_date="2022-01-01") assert len(results.outputs) == 4 @pytest.mark.parametrize( "file_path, aha_type, from_date", [ ("test_data/empty_feature_result.json", AHA_TYPE.FEATURES, "3000-01-01"), ("test_data/empty_idea_result.json", AHA_TYPE.IDEAS, "3000-01-01"), ], ) def test_getFeaturesFromDate(mocker, file_path, aha_type, from_date): """ When: - Requesting all features with created date of the future Then: - Return en empty list """ client = mock_client(mocker, util_load_json(file_path)) results = get_command(client=client, aha_type=aha_type, from_date=from_date) assert len(results.outputs) == 0 def test_getAFeature(mocker): """ When: - Requesting a specific feature Then: - Returns the requested feature """ client = mock_client(mocker, util_load_json("test_data/get_specific_feature.json")) result = get_command(client=client, aha_type=AHA_TYPE.FEATURES, from_date="2020-01-01", aha_object_name="DEMO-10") assert len(result.outputs) == 1 assert result.outputs[0]["reference_num"] == "DEMO-10" def test_getAnIdea(mocker): """ When: - Requesting a specific idea Then: - Returns the requested idea """ client = mock_client(mocker, util_load_json("test_data/get_specific_idea.json")) result = get_command(client=client, aha_type=AHA_TYPE.IDEAS, from_date="2020-01-01", aha_object_name="DEMO-I-2895") assert len(result.outputs) == 1 assert result.outputs[0]["reference_num"] == "DEMO-I-2895" def test_editFeatureField(mocker): """ When: - Requesting to update fields in a feature. Then: - Return the feature with updated fields. """ client = mock_client(mocker, util_load_json("test_data/update_feature_fields.json")) result = edit_command( client=client, aha_type=AHA_TYPE.FEATURES, aha_object_name="DEMO-10", fields='{"name": "DEMO-10", "description": "new description", "status": "Closed"}', ) assert len(result.outputs) == 1 output = result.outputs[0] assert output.get("name") == "Demo-10" assert output.get("description") == "test desc" assert output.get("workflow_status") == "Closed" def test_editIdeaStatus(mocker): """ When: - Requesting to update status in an idea. Then: - Return the idea with an updated field. """ client = mock_client(mocker, util_load_json("test_data/update_idea_status.json")) result = edit_command(client=client, aha_type=AHA_TYPE.IDEAS, aha_object_name="DEMO-I-2895", fields="{}") assert len(result.outputs) == 1 output = result.outputs[0] assert output.get("name") == "[Test] Mirroring" assert output.get("description") == "Aha Jira Mirroring" assert output.get("workflow_status") == "Shipped" def test_editSpecificFeatureField(mocker): """ When: - Requesting to update a specific field in a feature. Then: - Return the feature with only the specific field updated. """ new_name = "change just name" client = mock_client(mocker, util_load_json("test_data/update_feature_field.json")) result = edit_command( client=client, aha_type=AHA_TYPE.FEATURES, aha_object_name="DEMO-10", fields=f'{{"description": "{new_name}"}}' ) assert len(result.outputs) == 1 output = result.outputs[0] assert output.get("name") == new_name assert output.get("description") == "description" assert output.get("workflow_status") == "Closed"