browsergym-workarena 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. browsergym/workarena/__init__.py +13 -1
  2. browsergym/workarena/api/category.py +74 -0
  3. browsergym/workarena/api/change_request.py +87 -0
  4. browsergym/workarena/api/computer_asset.py +90 -0
  5. browsergym/workarena/api/cost_center.py +19 -0
  6. browsergym/workarena/api/expense_line.py +89 -0
  7. browsergym/workarena/api/incident.py +45 -0
  8. browsergym/workarena/api/knowledge.py +29 -0
  9. browsergym/workarena/api/problem.py +90 -0
  10. browsergym/workarena/api/report.py +183 -0
  11. browsergym/workarena/api/requested_items.py +63 -0
  12. browsergym/workarena/api/user.py +11 -8
  13. browsergym/workarena/api/utils.py +47 -3
  14. browsergym/workarena/config.py +21 -1
  15. browsergym/workarena/data_files/setup_files/forms/expected_incident_form_fields.json +1 -1
  16. browsergym/workarena/data_files/setup_files/forms/expected_request_item_form_fields.json +1 -0
  17. browsergym/workarena/data_files/setup_files/knowledge/protocols.json +46 -0
  18. browsergym/workarena/data_files/setup_files/knowledge/test.html +1 -0
  19. browsergym/workarena/data_files/setup_files/lists/expected_asset_list_columns.json +2 -24
  20. browsergym/workarena/data_files/setup_files/lists/expected_change_request_list_columns.json +4 -40
  21. browsergym/workarena/data_files/setup_files/lists/expected_expense_line_list_columns.json +12 -0
  22. browsergym/workarena/data_files/setup_files/lists/expected_hardware_list_columns.json +1 -42
  23. browsergym/workarena/data_files/setup_files/lists/expected_incident_list_columns.json +2 -18
  24. browsergym/workarena/data_files/setup_files/lists/expected_problem_list_columns.json +12 -0
  25. browsergym/workarena/data_files/setup_files/lists/expected_requested_items_list_columns.json +12 -0
  26. browsergym/workarena/data_files/setup_files/lists/expected_service_catalog_list_columns.json +2 -19
  27. browsergym/workarena/data_files/setup_files/lists/expected_user_list_columns.json +3 -50
  28. browsergym/workarena/data_files/task_configs/all_menu.json +1 -1
  29. browsergym/workarena/data_files/task_configs/dashboard_retrieval_minmax_task.json +1 -1
  30. browsergym/workarena/data_files/task_configs/dashboard_retrieval_value_task.json +1 -1
  31. browsergym/workarena/data_files/task_configs/filter_service_catalog_item_list_task.json +1 -1
  32. browsergym/workarena/data_files/task_configs/impersonation_users.json +1 -1
  33. browsergym/workarena/data_files/task_configs/report_retrieval_minmax_task.json +1 -1
  34. browsergym/workarena/data_files/task_configs/report_retrieval_value_task.json +1 -1
  35. browsergym/workarena/human_eval/console.js +176 -0
  36. browsergym/workarena/human_eval/tool.py +366 -0
  37. browsergym/workarena/install.py +81 -20
  38. browsergym/workarena/tasks/base.py +55 -20
  39. browsergym/workarena/tasks/comp_building_block.py +4 -0
  40. browsergym/workarena/tasks/compositional/__init__.py +76 -0
  41. browsergym/workarena/tasks/compositional/base.py +364 -0
  42. browsergym/workarena/tasks/compositional/dash_do_base.py +1366 -0
  43. browsergym/workarena/tasks/compositional/dash_do_catalog.py +1127 -0
  44. browsergym/workarena/tasks/compositional/dash_do_catalog_infeasible.py +2047 -0
  45. browsergym/workarena/tasks/compositional/dash_do_create_incident.py +403 -0
  46. browsergym/workarena/tasks/compositional/dash_do_create_incident_infeasible.py +278 -0
  47. browsergym/workarena/tasks/compositional/dash_do_create_problem.py +336 -0
  48. browsergym/workarena/tasks/compositional/dash_do_create_problem_infeasible.py +235 -0
  49. browsergym/workarena/tasks/compositional/dash_do_filter.py +1600 -0
  50. browsergym/workarena/tasks/compositional/dash_do_request_item.py +1315 -0
  51. browsergym/workarena/tasks/compositional/dash_do_request_item_infeasible.py +693 -0
  52. browsergym/workarena/tasks/compositional/delete_record.py +341 -0
  53. browsergym/workarena/tasks/compositional/edit_knowledge_base.py +457 -0
  54. browsergym/workarena/tasks/compositional/expense_management.py +598 -0
  55. browsergym/workarena/tasks/compositional/filter_and_do.py +139 -0
  56. browsergym/workarena/tasks/compositional/find_and_order_item.py +345 -0
  57. browsergym/workarena/tasks/compositional/manage_change_request_schedule.py +1417 -0
  58. browsergym/workarena/tasks/compositional/mark_duplicate_problems.py +499 -0
  59. browsergym/workarena/tasks/compositional/maximize_investment_return.py +1763 -0
  60. browsergym/workarena/tasks/compositional/navigate_and_do.py +1151 -0
  61. browsergym/workarena/tasks/compositional/navigate_and_do_infeasible.py +2100 -0
  62. browsergym/workarena/tasks/compositional/offboard_user.py +207 -0
  63. browsergym/workarena/tasks/compositional/onboard_user.py +226 -0
  64. browsergym/workarena/tasks/compositional/update_task.py +145 -0
  65. browsergym/workarena/tasks/compositional/utils/curriculum.py +215 -0
  66. browsergym/workarena/tasks/compositional/utils/infeasible_configs.py +151 -0
  67. browsergym/workarena/tasks/compositional/utils/knapsack.py +192 -0
  68. browsergym/workarena/tasks/compositional/warranty_check.py +227 -0
  69. browsergym/workarena/tasks/compositional/work_assignment.py +804 -0
  70. browsergym/workarena/tasks/compositional/workload_balancing.py +396 -0
  71. browsergym/workarena/tasks/dashboard.py +188 -8
  72. browsergym/workarena/tasks/form.py +1024 -232
  73. browsergym/workarena/tasks/knowledge.py +216 -25
  74. browsergym/workarena/tasks/list.py +519 -102
  75. browsergym/workarena/tasks/mark_duplicate_problem.py +171 -0
  76. browsergym/workarena/tasks/navigation.py +55 -13
  77. browsergym/workarena/tasks/scripts/extract_all_menu_items.py +9 -2
  78. browsergym/workarena/tasks/scripts/generate_dashboard_configs.py +6 -5
  79. browsergym/workarena/tasks/scripts/service_catalog.py +2 -1
  80. browsergym/workarena/tasks/scripts/validate.py +8 -2
  81. browsergym/workarena/tasks/send_chat_message.py +90 -0
  82. browsergym/workarena/tasks/service_catalog.py +94 -26
  83. browsergym/workarena/tasks/utils/form.py +1 -4
  84. browsergym/workarena/tasks/utils/private_tasks.py +63 -0
  85. browsergym/workarena/tasks/utils/utils.py +13 -0
  86. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/METADATA +19 -18
  87. browsergym_workarena-0.3.0.dist-info/RECORD +138 -0
  88. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/entry_points.txt +1 -0
  89. browsergym_workarena-0.2.1.dist-info/RECORD +0 -85
  90. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/WHEEL +0 -0
  91. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,18 @@
1
- __version__ = "0.2.1"
1
+ __version__ = "0.3.0"
2
+
3
+ import inspect
4
+ import numpy as np
2
5
 
3
6
  from browsergym.core.registration import register_task
4
7
 
8
+ from .tasks.comp_building_block import CompositionalBuildingBlockTask
5
9
  from .tasks.dashboard import __TASKS__ as DASHBOARD_TASKS
6
10
  from .tasks.form import __TASKS__ as FORM_TASKS
7
11
  from .tasks.knowledge import __TASKS__ as KB_TASKS
8
12
  from .tasks.list import __TASKS__ as LIST_TASKS
9
13
  from .tasks.navigation import __TASKS__ as NAVIGATION_TASKS
10
14
  from .tasks.service_catalog import __TASKS__ as SERVICE_CATALOG_TASKS
15
+ from .tasks.compositional.base import CompositionalTask
11
16
 
12
17
  ALL_WORKARENA_TASKS = [
13
18
  *DASHBOARD_TASKS,
@@ -17,6 +22,13 @@ ALL_WORKARENA_TASKS = [
17
22
  *NAVIGATION_TASKS,
18
23
  *SERVICE_CATALOG_TASKS,
19
24
  ]
25
+ ATOMIC_TASKS = [
26
+ task
27
+ for task in ALL_WORKARENA_TASKS
28
+ if inspect.isclass(task)
29
+ and not issubclass(task, CompositionalTask)
30
+ and not issubclass(task, CompositionalBuildingBlockTask)
31
+ ]
20
32
 
21
33
  # register the WorkArena benchmark
22
34
  for task in ALL_WORKARENA_TASKS:
@@ -0,0 +1,74 @@
1
+ import warnings
2
+ from faker import Faker
3
+
4
+ fake = Faker()
5
+
6
+ from .utils import table_api_call
7
+
8
+ from ..instance import SNowInstance
9
+
10
+
11
+ def create_category(
12
+ instance: SNowInstance,
13
+ list_name: str,
14
+ category_name: str = None,
15
+ ) -> list[str]:
16
+ """
17
+ NOTE: This function creates a new category in the given list. Because categories are in a drop-down list, adding more
18
+ categories will make the list longer and this will affect the difficulty of the task. Use only if you are certain you know
19
+ what you are doing.
20
+
21
+ Create a category for a given list
22
+
23
+ Parameters:
24
+ -----------
25
+ instance: SNowInstance
26
+ The instance to create the category in
27
+ list_name: str
28
+ The name of the list to create the category for (e.g. problem, incident, etc.)
29
+ category_name: str
30
+ The name of the category to create, defaults to a random category name
31
+
32
+ Returns:
33
+ --------
34
+ category_name, sys_id
35
+
36
+ """
37
+ warnings.warn(
38
+ "This function creates a new category in the given list. Because categories are in a drop-down list, adding more "
39
+ "categories will make the list longer and this will affect the difficulty of the task. Use only if you are certain you know "
40
+ "what you are doing.",
41
+ UserWarning,
42
+ )
43
+
44
+ if category_name is None:
45
+ category_name = fake.word() + "-" + fake.word()
46
+
47
+ # Create category
48
+ category_data = {
49
+ "name": list_name,
50
+ "element": "category",
51
+ "value": category_name,
52
+ }
53
+ result = table_api_call(
54
+ instance=instance,
55
+ table="sys_choice",
56
+ json=category_data,
57
+ method="POST",
58
+ wait_for_record=True,
59
+ )["result"]
60
+
61
+ sys_id = result["sys_id"]
62
+
63
+ return category_name, sys_id
64
+
65
+
66
+ def get_categories(instance, list_name):
67
+ """Get the name of the categories for a given list name"""
68
+ categories = table_api_call(
69
+ instance=instance,
70
+ table="sys_choice",
71
+ params={"sysparm_query": f"name={list_name}^element=category", "sysparm_fields": "value"},
72
+ )["result"]
73
+
74
+ return categories
@@ -0,0 +1,87 @@
1
+ import faker
2
+ import numpy as np
3
+
4
+ fake = faker.Faker()
5
+
6
+ from datetime import datetime, timedelta
7
+
8
+ from .category import get_categories
9
+ from .utils import table_api_call
10
+
11
+ from ..instance import SNowInstance
12
+
13
+
14
+ def create_change_request(
15
+ instance: SNowInstance,
16
+ user_sys_id: str,
17
+ impact: int,
18
+ risk: int,
19
+ start_date: datetime = "",
20
+ end_date: datetime = "",
21
+ hashtag: str = "",
22
+ short_description: str = None,
23
+ random: np.random = None,
24
+ ) -> list[str]:
25
+ """
26
+ Create a change request
27
+
28
+ Parameters:
29
+ -----------
30
+ instance: SNowInstance
31
+ The instance to create the change request in
32
+ user_sys_id: str
33
+ The sys_id of the user to assign the problem to
34
+ impact: str
35
+ The impact of the change request; ranges from 1 (high) to 3 (low)
36
+ risk: str
37
+ The risk of the change request; ranges from 2 (high) to 4 (low)
38
+ start_date: datetime.datetime
39
+ The start date of the change request; empty if not set
40
+ end_date: datetime.datetime
41
+ The end date of the change request; empty if not set
42
+ hashtag: str
43
+ The name of the hashtag for the change request. If "", no hashtag will be added
44
+ short_description: str
45
+ The short description of the change request. If None, a random sentence will be generated
46
+ random: np.random
47
+ The random number generator
48
+
49
+ Returns:
50
+ --------
51
+ sys_id of the change request
52
+ number of the change request
53
+
54
+ """
55
+ if short_description is None:
56
+ short_description = fake.sentence(4)
57
+ categories = get_categories(instance=instance, list_name="change_request")
58
+ category = random.choice(categories)
59
+
60
+ cfg = {
61
+ "reason": "broken",
62
+ "upon_reject": "cancel",
63
+ "type": "emergency",
64
+ "state": "-5",
65
+ "phase": "requested",
66
+ "impact": str(impact),
67
+ "active": "true",
68
+ "short_description": short_description + " " + hashtag,
69
+ "assigned_to": user_sys_id,
70
+ "start_date": str(start_date),
71
+ "end_date": str(end_date),
72
+ "upon_approval": "proceed",
73
+ "justification": fake.sentence(),
74
+ "implementation_plan": fake.sentence(),
75
+ "phase_state": "open",
76
+ "risk": str(risk),
77
+ "cab_required": "false",
78
+ "category": category,
79
+ }
80
+ result = table_api_call(
81
+ instance=instance,
82
+ table="change_request",
83
+ method="POST",
84
+ json=cfg,
85
+ )["result"]
86
+
87
+ return result["sys_id"], result["number"]
@@ -0,0 +1,90 @@
1
+ import json
2
+ import numpy as np
3
+ import time
4
+
5
+ from faker import Faker
6
+
7
+ fake = Faker()
8
+
9
+ from ..instance import SNowInstance
10
+ from .utils import table_api_call
11
+
12
+
13
+ def create_computer_asset(
14
+ instance: SNowInstance,
15
+ asset_tag: str,
16
+ warranty_expiration_date: str = None,
17
+ user_sys_id: str = None,
18
+ computer_model_info: dict = None,
19
+ random: np.random = None,
20
+ ):
21
+ """Create a hardware asset -computer model- and assign it to a user
22
+ Args:
23
+ --------
24
+ instance (SNowInstance):
25
+ The instance to create the hardware asset in
26
+ asset_tag (str):
27
+ The asset tag of the hardware asset
28
+ warranty_expiration_date (str):
29
+ The warranty expiration date of the hardware asset. If None, a random date is chosen
30
+ user_sys_id (str):
31
+ The sys_id of the user to assign the hardware asset to. If None, the hardware asset is not assigned to any user
32
+ computer_model_info (dict):
33
+ Contains the sys_id and short_description of the computer model to create the hardware asset with.
34
+ If None, a random computer model is chosen
35
+ random (np.random):
36
+ The random number generator
37
+ Returns:
38
+ --------
39
+ sys_id (str):
40
+ The sys_id of the created hardware asset
41
+ computer_model (dict):
42
+ The computer model information
43
+ warranty_expiration_date (str):
44
+ The warranty expiration date of the hardware asset
45
+ """
46
+
47
+ if computer_model_info is None:
48
+ # Get the sys_id of the 'Computer' category
49
+ computer_model_sys_id = table_api_call(
50
+ instance=instance,
51
+ table="cmdb_model_category",
52
+ # The cmdb_model_category is the sys_id for the hardware category; computer in this case
53
+ params={
54
+ "sysparm_query": f"name=Computer",
55
+ "sysparm_fields": "sys_id",
56
+ },
57
+ )["result"][0]["sys_id"]
58
+ # Randomly choose a computer model if needed
59
+ computer_models = table_api_call(
60
+ instance=instance,
61
+ table="cmdb_model",
62
+ # The cmdb_model_category is the sys_id for the hardware category;
63
+ params={
64
+ "sysparm_query": f"cmdb_model_category={computer_model_sys_id}",
65
+ "sysparm_fields": "sys_id,short_description",
66
+ },
67
+ )["result"]
68
+ computer_model = random.choice(computer_models)
69
+ if warranty_expiration_date is None:
70
+ # Warranty expiration date is randomly selected between 1 year ago and 1 year from now
71
+ warranty_expiration_date = str(fake.date_between(start_date="-1y", end_date="+1y"))
72
+
73
+ # Create hardware asset
74
+ hardware_result = table_api_call(
75
+ instance=instance,
76
+ table="alm_hardware",
77
+ data=json.dumps(
78
+ {
79
+ "assigned_to": user_sys_id,
80
+ "asset_tag": asset_tag,
81
+ "display_name": asset_tag + " - " + computer_model["short_description"],
82
+ "model": computer_model["sys_id"],
83
+ "model_category": computer_model_sys_id,
84
+ "warranty_expiration": warranty_expiration_date,
85
+ }
86
+ ),
87
+ method="POST",
88
+ )["result"]
89
+
90
+ return hardware_result["sys_id"], computer_model, warranty_expiration_date
@@ -0,0 +1,19 @@
1
+ import warnings
2
+ from faker import Faker
3
+
4
+ fake = Faker()
5
+
6
+ from .utils import table_api_call
7
+
8
+ from ..instance import SNowInstance
9
+
10
+
11
+ def get_cost_center_sysid(instance, cost_center_name):
12
+ """Get the sys_id of a cost center by its name"""
13
+ sys_id = table_api_call(
14
+ instance=instance,
15
+ table="cmn_cost_center",
16
+ params={"sysparm_query": f"name={cost_center_name}", "sysparm_fields": "sys_id"},
17
+ )["result"][0]
18
+
19
+ return sys_id
@@ -0,0 +1,89 @@
1
+ import json
2
+ import time
3
+
4
+ from faker import Faker
5
+
6
+ fake = Faker()
7
+
8
+ from .cost_center import get_cost_center_sysid
9
+ from .utils import table_api_call
10
+
11
+ from ..instance import SNowInstance
12
+
13
+
14
+ def create_expense_line(
15
+ instance: SNowInstance,
16
+ amount: float,
17
+ number: str,
18
+ date: str,
19
+ short_description: str = None,
20
+ expense_hashtag: str = "",
21
+ task_sys_id: str = None,
22
+ cost_center_sys_id: str = None,
23
+ summary_type: str = "run_business",
24
+ user_sys_id: str = None,
25
+ ):
26
+ """Create a hardware asset -computer model- and assign it to a user
27
+ Args:
28
+ --------
29
+ instance (SNowInstance):
30
+ The instance to create the hardware asset in
31
+ amount (float):
32
+ The amount of the expense line
33
+ number (str):
34
+ The number of the expense line
35
+ date (str):
36
+ The date of the expense line
37
+ short_description (str):
38
+ The short description of the expense line; if None, a random one will be generated
39
+ expense_hashtag (str):
40
+ The hashtag of the expense line (added to the short description)
41
+ task_sys_id (str):
42
+ The sys id of the task to file the expense line under
43
+ cost_center_sys_id (str):
44
+ The sys id of the cost center to file the expense line under
45
+ summary_type (str):
46
+ The summary type of the expense line (choice of "run_business", "grow_business", "transform_business")
47
+ user_sys_id (str):
48
+ The sys_id of the user to assign the hardware asset to. If None, the hardware asset is not assigned to any user
49
+ Returns:
50
+ --------
51
+ sys_id (str):
52
+ The sys_id of the created expense_line
53
+ expense_line_number (str):
54
+ The number of the created expense_line
55
+ """
56
+ if cost_center_sys_id is None:
57
+ # sys_id of the engineering cost center
58
+ cost_center_sys_id = table_api_call(
59
+ instance=instance,
60
+ table="cmn_cost_center",
61
+ params={"sysparm_query": "name=Engineering"},
62
+ )["result"][0]["sys_id"]
63
+
64
+ if short_description is None:
65
+ short_description = fake.sentence(4)
66
+
67
+ expense_cfg = {
68
+ "date": date,
69
+ "base_expense": "",
70
+ "short_description": short_description + " " + expense_hashtag,
71
+ "summary_type": summary_type,
72
+ "summary_type": "run_business",
73
+ "type": "one-time",
74
+ "number": f"{number}",
75
+ "task": f"{task_sys_id}",
76
+ "state": "processed",
77
+ "amount": f"{amount}",
78
+ "cost_center": f"{cost_center_sys_id}",
79
+ "user": f"{user_sys_id}",
80
+ }
81
+
82
+ result = table_api_call(
83
+ instance=instance,
84
+ table="fm_expense_line",
85
+ json=expense_cfg,
86
+ method="POST",
87
+ )["result"]
88
+
89
+ return result["sys_id"], result["number"]
@@ -0,0 +1,45 @@
1
+ from faker import Faker
2
+ from ..instance import SNowInstance
3
+
4
+ fake = Faker()
5
+
6
+ from .utils import table_api_call
7
+
8
+
9
+ def create_incident(
10
+ instance: SNowInstance,
11
+ incident_number: int,
12
+ caller_sys_id: str,
13
+ category: str,
14
+ impact: int,
15
+ urgency: int,
16
+ priority: int,
17
+ incident_hastag: str = None,
18
+ assigned_to: str = None,
19
+ ):
20
+ incident_config = {
21
+ "task_effective_number": incident_number,
22
+ "number": incident_number,
23
+ "state": 2,
24
+ "knowledge": False,
25
+ "impact": impact,
26
+ "active": True,
27
+ "priority": priority,
28
+ "caller_id": caller_sys_id,
29
+ "short_description": incident_hastag if incident_hastag else " ".join(fake.words(5)),
30
+ "description": " ".join(fake.words(10)),
31
+ "incident_state": 2,
32
+ "urgency": urgency,
33
+ "severity": 3,
34
+ "category": category,
35
+ }
36
+ if assigned_to:
37
+ incident_config["assigned_to"] = assigned_to
38
+
39
+ incident_response = table_api_call(
40
+ instance=instance,
41
+ table="incident",
42
+ json=incident_config,
43
+ method="POST",
44
+ )["result"]
45
+ return incident_response
@@ -0,0 +1,29 @@
1
+ from ..instance import SNowInstance
2
+ from .utils import table_api_call
3
+
4
+
5
+ def give_kb_read_permissions(admin_instance, user_sys_id, user_name, kb_sys_id, kb_name):
6
+ # Need admin permissions to give KB permissions to the user
7
+
8
+ # Create user criteria
9
+ user_criteria_data = {
10
+ "user": user_sys_id,
11
+ "name": f"{user_name} read KB",
12
+ "short_description": f"Let {user_name} read {kb_name}",
13
+ }
14
+ criteria_response = table_api_call(
15
+ instance=admin_instance, table="user_criteria", json=user_criteria_data, method="POST"
16
+ )["result"]
17
+ criteria_sys_id = criteria_response["sys_id"]
18
+
19
+ # Add user criteria entry to allow users to access the ADHOC KB
20
+ kb_uc_can_read_mtom_data = {
21
+ "user_criteria": criteria_sys_id,
22
+ "kb_knowledge_base": kb_sys_id,
23
+ }
24
+ _ = table_api_call(
25
+ instance=admin_instance,
26
+ table="kb_uc_can_read_mtom",
27
+ json=kb_uc_can_read_mtom_data,
28
+ method="POST",
29
+ )["result"]
@@ -0,0 +1,90 @@
1
+ import faker
2
+
3
+ fake = faker.Faker()
4
+
5
+ from ..instance import SNowInstance
6
+ from .utils import table_api_call
7
+
8
+
9
+ def create_problem(
10
+ instance: SNowInstance,
11
+ priority: str,
12
+ user_sys_id: str,
13
+ problem_hashtag: str,
14
+ short_description: str = None,
15
+ return_number: bool = False,
16
+ ) -> list[str]:
17
+ """
18
+ Create a problem with a random cause, description, and short description. The problem is assigned to a user and
19
+ is created with a hashtag.
20
+
21
+ Parameters:
22
+ -----------
23
+ instance: SNowInstance
24
+ The instance to create the problem in
25
+ priority: str
26
+ The priority of the problem
27
+ user_sys_id: str
28
+ The sys_id of the user to assign the problem to
29
+ problem_hashtag: str
30
+ The name of the hashtag for the problem
31
+ short_description: str
32
+ The short description of the problem (optional). if not provided, a random one will be generated
33
+ return_number: bool
34
+ whether or not to return the problem number that was created
35
+
36
+ Returns:
37
+ --------
38
+ sys_id of the problem
39
+ problem_number (optional)
40
+
41
+ """
42
+ cause = fake.sentence()
43
+ description = fake.text()
44
+ if short_description is None:
45
+ short_description = fake.sentence(4)
46
+
47
+ # Priority is a read-only field defined by a combo of impact and urgency. The mapping is as follows:
48
+ # https://docs.servicenow.com/bundle/washingtondc-it-service-management/page/product/problem-management/concept/prioritise-problems.html
49
+ priority_to_impact_and_urgency = {
50
+ 1: (1, 1),
51
+ 2: (1, 2),
52
+ 3: (1, 3),
53
+ 4: (2, 3),
54
+ 5: (3, 3),
55
+ }
56
+
57
+ impact, urgency = priority_to_impact_and_urgency[priority]
58
+
59
+ problem_cfg = {
60
+ "made_sla": True,
61
+ "upon_reject": "cancel",
62
+ "cause_notes": f" <p>{cause}</p> ",
63
+ "fix_notes": " placeholder ", # placeholder value - will not work without a fix note
64
+ "knowledge": False,
65
+ "major_problem": False,
66
+ "impact": f"{impact}",
67
+ "active": False,
68
+ "sys_domain_path": "/",
69
+ "short_description": f"{short_description} {problem_hashtag}",
70
+ "known_error": False,
71
+ "description": f"{description}",
72
+ "sla_due": "2021-04-11 17:39:07",
73
+ "closed_at": "",
74
+ "resolution_code": "fix_applied",
75
+ "urgency": f"{urgency}",
76
+ "assigned_to": f"{user_sys_id}",
77
+ "active": True,
78
+ }
79
+
80
+ result = table_api_call(
81
+ instance=instance,
82
+ table="problem",
83
+ json=problem_cfg,
84
+ method="POST",
85
+ )["result"]
86
+
87
+ if return_number:
88
+ return result["sys_id"], result["number"]
89
+
90
+ return result["sys_id"]