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.
- browsergym/workarena/__init__.py +13 -1
- browsergym/workarena/api/category.py +74 -0
- browsergym/workarena/api/change_request.py +87 -0
- browsergym/workarena/api/computer_asset.py +90 -0
- browsergym/workarena/api/cost_center.py +19 -0
- browsergym/workarena/api/expense_line.py +89 -0
- browsergym/workarena/api/incident.py +45 -0
- browsergym/workarena/api/knowledge.py +29 -0
- browsergym/workarena/api/problem.py +90 -0
- browsergym/workarena/api/report.py +183 -0
- browsergym/workarena/api/requested_items.py +63 -0
- browsergym/workarena/api/user.py +11 -8
- browsergym/workarena/api/utils.py +47 -3
- browsergym/workarena/config.py +21 -1
- browsergym/workarena/data_files/setup_files/forms/expected_incident_form_fields.json +1 -1
- browsergym/workarena/data_files/setup_files/forms/expected_request_item_form_fields.json +1 -0
- browsergym/workarena/data_files/setup_files/knowledge/protocols.json +46 -0
- browsergym/workarena/data_files/setup_files/knowledge/test.html +1 -0
- browsergym/workarena/data_files/setup_files/lists/expected_asset_list_columns.json +2 -24
- browsergym/workarena/data_files/setup_files/lists/expected_change_request_list_columns.json +4 -40
- browsergym/workarena/data_files/setup_files/lists/expected_expense_line_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_hardware_list_columns.json +1 -42
- browsergym/workarena/data_files/setup_files/lists/expected_incident_list_columns.json +2 -18
- browsergym/workarena/data_files/setup_files/lists/expected_problem_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_requested_items_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_service_catalog_list_columns.json +2 -19
- browsergym/workarena/data_files/setup_files/lists/expected_user_list_columns.json +3 -50
- browsergym/workarena/data_files/task_configs/all_menu.json +1 -1
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_minmax_task.json +1 -1
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_value_task.json +1 -1
- browsergym/workarena/data_files/task_configs/filter_service_catalog_item_list_task.json +1 -1
- browsergym/workarena/data_files/task_configs/impersonation_users.json +1 -1
- browsergym/workarena/data_files/task_configs/report_retrieval_minmax_task.json +1 -1
- browsergym/workarena/data_files/task_configs/report_retrieval_value_task.json +1 -1
- browsergym/workarena/human_eval/console.js +176 -0
- browsergym/workarena/human_eval/tool.py +366 -0
- browsergym/workarena/install.py +81 -20
- browsergym/workarena/tasks/base.py +55 -20
- browsergym/workarena/tasks/comp_building_block.py +4 -0
- browsergym/workarena/tasks/compositional/__init__.py +76 -0
- browsergym/workarena/tasks/compositional/base.py +364 -0
- browsergym/workarena/tasks/compositional/dash_do_base.py +1366 -0
- browsergym/workarena/tasks/compositional/dash_do_catalog.py +1127 -0
- browsergym/workarena/tasks/compositional/dash_do_catalog_infeasible.py +2047 -0
- browsergym/workarena/tasks/compositional/dash_do_create_incident.py +403 -0
- browsergym/workarena/tasks/compositional/dash_do_create_incident_infeasible.py +278 -0
- browsergym/workarena/tasks/compositional/dash_do_create_problem.py +336 -0
- browsergym/workarena/tasks/compositional/dash_do_create_problem_infeasible.py +235 -0
- browsergym/workarena/tasks/compositional/dash_do_filter.py +1600 -0
- browsergym/workarena/tasks/compositional/dash_do_request_item.py +1315 -0
- browsergym/workarena/tasks/compositional/dash_do_request_item_infeasible.py +693 -0
- browsergym/workarena/tasks/compositional/delete_record.py +341 -0
- browsergym/workarena/tasks/compositional/edit_knowledge_base.py +457 -0
- browsergym/workarena/tasks/compositional/expense_management.py +598 -0
- browsergym/workarena/tasks/compositional/filter_and_do.py +139 -0
- browsergym/workarena/tasks/compositional/find_and_order_item.py +345 -0
- browsergym/workarena/tasks/compositional/manage_change_request_schedule.py +1417 -0
- browsergym/workarena/tasks/compositional/mark_duplicate_problems.py +499 -0
- browsergym/workarena/tasks/compositional/maximize_investment_return.py +1763 -0
- browsergym/workarena/tasks/compositional/navigate_and_do.py +1151 -0
- browsergym/workarena/tasks/compositional/navigate_and_do_infeasible.py +2100 -0
- browsergym/workarena/tasks/compositional/offboard_user.py +207 -0
- browsergym/workarena/tasks/compositional/onboard_user.py +226 -0
- browsergym/workarena/tasks/compositional/update_task.py +145 -0
- browsergym/workarena/tasks/compositional/utils/curriculum.py +215 -0
- browsergym/workarena/tasks/compositional/utils/infeasible_configs.py +151 -0
- browsergym/workarena/tasks/compositional/utils/knapsack.py +192 -0
- browsergym/workarena/tasks/compositional/warranty_check.py +227 -0
- browsergym/workarena/tasks/compositional/work_assignment.py +804 -0
- browsergym/workarena/tasks/compositional/workload_balancing.py +396 -0
- browsergym/workarena/tasks/dashboard.py +188 -8
- browsergym/workarena/tasks/form.py +1024 -232
- browsergym/workarena/tasks/knowledge.py +216 -25
- browsergym/workarena/tasks/list.py +519 -102
- browsergym/workarena/tasks/mark_duplicate_problem.py +171 -0
- browsergym/workarena/tasks/navigation.py +55 -13
- browsergym/workarena/tasks/scripts/extract_all_menu_items.py +9 -2
- browsergym/workarena/tasks/scripts/generate_dashboard_configs.py +6 -5
- browsergym/workarena/tasks/scripts/service_catalog.py +2 -1
- browsergym/workarena/tasks/scripts/validate.py +8 -2
- browsergym/workarena/tasks/send_chat_message.py +90 -0
- browsergym/workarena/tasks/service_catalog.py +94 -26
- browsergym/workarena/tasks/utils/form.py +1 -4
- browsergym/workarena/tasks/utils/private_tasks.py +63 -0
- browsergym/workarena/tasks/utils/utils.py +13 -0
- {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/METADATA +19 -18
- browsergym_workarena-0.3.0.dist-info/RECORD +138 -0
- {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/entry_points.txt +1 -0
- browsergym_workarena-0.2.1.dist-info/RECORD +0 -85
- {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/WHEEL +0 -0
- {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from faker import Faker
|
|
4
|
+
|
|
5
|
+
fake = Faker()
|
|
6
|
+
from playwright.sync_api._generated import Page
|
|
7
|
+
|
|
8
|
+
from .base import CompositionalTask, HumanEvalTask
|
|
9
|
+
from .delete_record import DeleteUserTask
|
|
10
|
+
|
|
11
|
+
from ..base import AbstractServiceNowTask
|
|
12
|
+
from ..form import EditHardwareAssetTask
|
|
13
|
+
from ..knowledge import KnowledgeBaseSearchTask
|
|
14
|
+
from ..list import FilterHardwareListTask
|
|
15
|
+
from ..navigation import AllMenuTask
|
|
16
|
+
|
|
17
|
+
from ...api.computer_asset import create_computer_asset
|
|
18
|
+
from ...api.user import create_user
|
|
19
|
+
from ...api.utils import table_api_call, db_delete_from_table
|
|
20
|
+
from ...instance import SNowInstance
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OffBoardUserTask(CompositionalTask, HumanEvalTask):
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
seed: int = None,
|
|
27
|
+
instance: SNowInstance = None,
|
|
28
|
+
fixed_config: list[AbstractServiceNowTask] = None,
|
|
29
|
+
level: int = 2,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Employee OffBoarding Task
|
|
33
|
+
|
|
34
|
+
Parameters:
|
|
35
|
+
-----------
|
|
36
|
+
instance: SNowInstance
|
|
37
|
+
The ServiceNow instance to run the task on.
|
|
38
|
+
fixed_config: list[AbstractServiceNowTask]
|
|
39
|
+
A list of subtasks.
|
|
40
|
+
level: int
|
|
41
|
+
The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
|
|
42
|
+
L3 will start in a private task page describing the information needed to complete the task and the related company protocol
|
|
43
|
+
to complete it.
|
|
44
|
+
Attributes:
|
|
45
|
+
-----------
|
|
46
|
+
task_description: str
|
|
47
|
+
The start of the task description to be completed. e.g. "Referring to company protocol 'Offboarding a user', offboard user XYZ"
|
|
48
|
+
short_description: str
|
|
49
|
+
A short description of the task to be completed. e.g. "Offboard user John Doe"
|
|
50
|
+
"""
|
|
51
|
+
assert level in [2, 3], "Level must be either 2 or 3"
|
|
52
|
+
self.level = level
|
|
53
|
+
self.protocol_name = "Offboarding a user"
|
|
54
|
+
super().__init__(
|
|
55
|
+
seed=seed,
|
|
56
|
+
instance=instance,
|
|
57
|
+
fixed_config=fixed_config,
|
|
58
|
+
level=level,
|
|
59
|
+
protocol_name=self.protocol_name,
|
|
60
|
+
)
|
|
61
|
+
self.task_description = None
|
|
62
|
+
self.short_description = None
|
|
63
|
+
self.user_full_name = None
|
|
64
|
+
self.user_sys_id = None
|
|
65
|
+
self.user_name = None
|
|
66
|
+
self.laptop_asset_tag = None
|
|
67
|
+
self.laptop_sys_id = None
|
|
68
|
+
|
|
69
|
+
def setup_goal(self, page: Page) -> tuple[str, dict]:
|
|
70
|
+
# Generate random name for the user
|
|
71
|
+
first_name = fake.first_name() + "-" + fake.first_name()
|
|
72
|
+
last_name = fake.last_name() + "-" + fake.last_name()
|
|
73
|
+
self.user_full_name = first_name + " " + last_name
|
|
74
|
+
self.laptop_asset_tag = "P" + str(id(self) % (10**8)).zfill(8)
|
|
75
|
+
|
|
76
|
+
# Create user
|
|
77
|
+
self.user_name, _, self.user_sys_id = create_user(
|
|
78
|
+
instance=self.instance, first_name=first_name, last_name=last_name, random=self.random
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
assert self.user_sys_id, f"Failed to create user {first_name} {last_name}"
|
|
82
|
+
|
|
83
|
+
self.laptop_sys_id, _, _ = create_computer_asset(
|
|
84
|
+
instance=self.instance,
|
|
85
|
+
asset_tag=self.laptop_asset_tag,
|
|
86
|
+
user_sys_id=self.user_sys_id,
|
|
87
|
+
random=self.random,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
config = self.fixed_config if self.fixed_config else self._get_config()
|
|
91
|
+
# Get the task description
|
|
92
|
+
self.short_description = f"Offboard user {self.user_full_name}"
|
|
93
|
+
self.task_description = f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) offboard user "{self.user_full_name}" \n'
|
|
94
|
+
|
|
95
|
+
goal, info = super().setup_goal(page=page, config=config)
|
|
96
|
+
|
|
97
|
+
return goal, info
|
|
98
|
+
|
|
99
|
+
def _get_config(self) -> list[AbstractServiceNowTask]:
|
|
100
|
+
"""Sample a user configuration and a hardware asset configuration. Add the assigned_to field if missing
|
|
101
|
+
from the hardware asset configuration. Finally, return the list of subtasks, with navigation subtasks included.
|
|
102
|
+
"""
|
|
103
|
+
navigate_to_protocol_subtask = [
|
|
104
|
+
# Navigate to the KB
|
|
105
|
+
AllMenuTask(
|
|
106
|
+
instance=self.instance,
|
|
107
|
+
fixed_config={
|
|
108
|
+
"application": "Self-Service",
|
|
109
|
+
"module": "Knowledge",
|
|
110
|
+
"url": "/now/nav/ui/classic/params/target/%24knowledge.do",
|
|
111
|
+
},
|
|
112
|
+
is_validated=False,
|
|
113
|
+
used_in_level_2=False,
|
|
114
|
+
),
|
|
115
|
+
# Find the protocol for on-boarding a new user
|
|
116
|
+
KnowledgeBaseSearchTask(
|
|
117
|
+
instance=self.instance,
|
|
118
|
+
fixed_config={
|
|
119
|
+
"alternative_answers": [],
|
|
120
|
+
"item": f"{self.protocol_name}",
|
|
121
|
+
"question": f'Can you find the "{self.protocol_name}" Protocol in the Knowledge Base?',
|
|
122
|
+
"value": "",
|
|
123
|
+
},
|
|
124
|
+
is_validated=False,
|
|
125
|
+
used_in_level_2=False,
|
|
126
|
+
),
|
|
127
|
+
]
|
|
128
|
+
unassign_hardware_subtask = [
|
|
129
|
+
# Navigate to the hardware asset list
|
|
130
|
+
AllMenuTask(
|
|
131
|
+
instance=self.instance,
|
|
132
|
+
fixed_config={
|
|
133
|
+
"application": "Asset",
|
|
134
|
+
"module": "Portfolios > Hardware Assets",
|
|
135
|
+
"url": "/now/nav/ui/classic/params/target/alm_hardware_list.do",
|
|
136
|
+
},
|
|
137
|
+
is_validated=False,
|
|
138
|
+
used_in_level_2=True,
|
|
139
|
+
),
|
|
140
|
+
FilterHardwareListTask(
|
|
141
|
+
instance=self.instance,
|
|
142
|
+
fixed_config={
|
|
143
|
+
"filter_columns": ["assigned_to"],
|
|
144
|
+
"filter_kind": "AND",
|
|
145
|
+
"filter_values": [f"{self.user_full_name}"],
|
|
146
|
+
},
|
|
147
|
+
is_validated=False,
|
|
148
|
+
used_in_level_2=True,
|
|
149
|
+
),
|
|
150
|
+
# Create a new hardware asset
|
|
151
|
+
EditHardwareAssetTask(
|
|
152
|
+
instance=self.instance,
|
|
153
|
+
record_sys_id=self.laptop_sys_id,
|
|
154
|
+
new_values={"assigned_to": ""},
|
|
155
|
+
is_validated=True,
|
|
156
|
+
used_in_level_2=True,
|
|
157
|
+
level=self.level,
|
|
158
|
+
),
|
|
159
|
+
]
|
|
160
|
+
delete_user_subtask = [
|
|
161
|
+
# Navigate to the user list
|
|
162
|
+
AllMenuTask(
|
|
163
|
+
instance=self.instance,
|
|
164
|
+
fixed_config={
|
|
165
|
+
"application": "System Security",
|
|
166
|
+
"module": "Users and Groups > Users",
|
|
167
|
+
"url": "/now/nav/ui/classic/params/target/sys_user_list.do",
|
|
168
|
+
},
|
|
169
|
+
is_validated=False,
|
|
170
|
+
used_in_level_2=True,
|
|
171
|
+
),
|
|
172
|
+
# Create a new user
|
|
173
|
+
DeleteUserTask(
|
|
174
|
+
instance=self.instance,
|
|
175
|
+
fixed_config={
|
|
176
|
+
"field_name": "name",
|
|
177
|
+
"pretty_printed_field_name": "Name",
|
|
178
|
+
"field_value": self.user_full_name,
|
|
179
|
+
"other_fields": {},
|
|
180
|
+
},
|
|
181
|
+
record_sys_id=self.user_sys_id,
|
|
182
|
+
is_validated=True,
|
|
183
|
+
used_in_level_2=True,
|
|
184
|
+
),
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
config = navigate_to_protocol_subtask + unassign_hardware_subtask + delete_user_subtask
|
|
188
|
+
|
|
189
|
+
return config
|
|
190
|
+
|
|
191
|
+
def teardown(self) -> None:
|
|
192
|
+
# Delete the user
|
|
193
|
+
user_record = table_api_call(
|
|
194
|
+
instance=self.instance,
|
|
195
|
+
table="sys_user",
|
|
196
|
+
params={"sysparm_query": f"sys_id={self.user_sys_id}"},
|
|
197
|
+
)["result"]
|
|
198
|
+
if user_record:
|
|
199
|
+
db_delete_from_table(
|
|
200
|
+
instance=self.instance,
|
|
201
|
+
table="sys_user",
|
|
202
|
+
sys_id=self.user_sys_id,
|
|
203
|
+
)
|
|
204
|
+
super().teardown()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
__TASKS__ = [OffBoardUserTask]
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from playwright.sync_api._generated import Page
|
|
4
|
+
|
|
5
|
+
from .base import CompositionalTask, HumanEvalTask
|
|
6
|
+
|
|
7
|
+
from ..base import AbstractServiceNowTask
|
|
8
|
+
from ..form import CreateUserTask, CreateHardwareAssetTask
|
|
9
|
+
from ..knowledge import KnowledgeBaseSearchTask
|
|
10
|
+
from ..navigation import AllMenuTask
|
|
11
|
+
from ..service_catalog import OrderAppleMacBookPro15Task
|
|
12
|
+
|
|
13
|
+
from ...instance import SNowInstance
|
|
14
|
+
from ...config import CREATE_USER_CONFIG_PATH, CREATE_HARDWARE_CONFIG_PATH
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OnBoardUserTask(CompositionalTask, HumanEvalTask):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
seed: int = None,
|
|
21
|
+
instance: SNowInstance = None,
|
|
22
|
+
fixed_config: list[AbstractServiceNowTask] = None,
|
|
23
|
+
level: int = 2,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Create a compositional task with specific subtasks
|
|
27
|
+
|
|
28
|
+
Parameters:
|
|
29
|
+
-----------
|
|
30
|
+
instance: SNowInstance
|
|
31
|
+
The ServiceNow instance to run the task on.
|
|
32
|
+
fixed_config: list[AbstractServiceNowTask]
|
|
33
|
+
A list of subtasks.
|
|
34
|
+
level: int
|
|
35
|
+
The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
|
|
36
|
+
L3 will start in a private task page describing the information needed to complete the task and the related company protocol
|
|
37
|
+
to complete it.
|
|
38
|
+
Attributes:
|
|
39
|
+
-----------
|
|
40
|
+
task_description: str
|
|
41
|
+
The start of the task description to be completed. e.g. "Referring to company protocol 'Onboarding a new user', onboard user with the following information: \n"
|
|
42
|
+
short_description: str
|
|
43
|
+
A short description of the task to be completed. e.g. "Onboard user John Doe"
|
|
44
|
+
"""
|
|
45
|
+
assert level in [2, 3], "Level must be either 2 or 3"
|
|
46
|
+
self.level = level
|
|
47
|
+
self.protocol_name = "Onboarding a new user"
|
|
48
|
+
super().__init__(
|
|
49
|
+
seed=seed,
|
|
50
|
+
instance=instance,
|
|
51
|
+
fixed_config=fixed_config,
|
|
52
|
+
level=level,
|
|
53
|
+
protocol_name=self.protocol_name,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
self.all_user_configs = CreateUserTask.all_configs()
|
|
57
|
+
self.all_hardware_asset_configs = CreateHardwareAssetTask.all_configs()
|
|
58
|
+
self.task_description = None
|
|
59
|
+
self.short_description = None
|
|
60
|
+
|
|
61
|
+
def setup_goal(self, page: Page) -> tuple[str, dict]:
|
|
62
|
+
# Sample a configuration
|
|
63
|
+
config = self.fixed_config if self.fixed_config else self._get_config()
|
|
64
|
+
user_name = (
|
|
65
|
+
config[3].fixed_config["template_record"]["first_name"]
|
|
66
|
+
+ " "
|
|
67
|
+
+ config[3].fixed_config["template_record"]["last_name"]
|
|
68
|
+
)
|
|
69
|
+
# Get the task description
|
|
70
|
+
self.short_description = f"Onboard user {user_name}"
|
|
71
|
+
self.task_description = f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) onboard user with the following information: \n'
|
|
72
|
+
|
|
73
|
+
goal, info = super().setup_goal(page=page, config=config)
|
|
74
|
+
|
|
75
|
+
return goal, info
|
|
76
|
+
|
|
77
|
+
def _get_config(self) -> list[AbstractServiceNowTask]:
|
|
78
|
+
# Sample base configurations; the hardware config will be modified to include the assigned_to field
|
|
79
|
+
user_config = self.random.choice(self.all_user_configs)
|
|
80
|
+
hardware_config = self.random.choice(self.all_hardware_asset_configs)
|
|
81
|
+
|
|
82
|
+
# Get the common fields between the user and hardware configurations to adjust the hardware config
|
|
83
|
+
common_fields = [
|
|
84
|
+
field for field in hardware_config["fields"].keys() if field in user_config["fields"]
|
|
85
|
+
]
|
|
86
|
+
common_task_fields = [
|
|
87
|
+
field for field in hardware_config["task_fields"] if field in user_config["task_fields"]
|
|
88
|
+
]
|
|
89
|
+
common_template_record_fields = [
|
|
90
|
+
field
|
|
91
|
+
for field in hardware_config["template_record"].keys()
|
|
92
|
+
if field in user_config["template_record"] and "sys" not in field
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# Drop the common fields as they create synchronization issues
|
|
96
|
+
for field in common_fields + common_task_fields + common_template_record_fields:
|
|
97
|
+
if field in user_config["fields"]:
|
|
98
|
+
user_config["fields"].pop(field)
|
|
99
|
+
if field in hardware_config["fields"]:
|
|
100
|
+
hardware_config["fields"].pop(field)
|
|
101
|
+
|
|
102
|
+
if field in user_config["task_fields"]:
|
|
103
|
+
user_config["task_fields"].remove(field)
|
|
104
|
+
if field in hardware_config["task_fields"]:
|
|
105
|
+
hardware_config["task_fields"].remove(field)
|
|
106
|
+
|
|
107
|
+
if field in user_config["template_record"]:
|
|
108
|
+
user_config["template_record"].pop(field)
|
|
109
|
+
if field in hardware_config["template_record"]:
|
|
110
|
+
hardware_config["template_record"].pop(field)
|
|
111
|
+
|
|
112
|
+
navigate_to_protocol_subtask = [
|
|
113
|
+
# Navigate to the KB
|
|
114
|
+
AllMenuTask(
|
|
115
|
+
instance=self.instance,
|
|
116
|
+
fixed_config={
|
|
117
|
+
"application": "Self-Service",
|
|
118
|
+
"module": "Knowledge",
|
|
119
|
+
"url": "/now/nav/ui/classic/params/target/%24knowledge.do",
|
|
120
|
+
},
|
|
121
|
+
is_validated=False,
|
|
122
|
+
used_in_level_2=False,
|
|
123
|
+
),
|
|
124
|
+
# Find the protocol for on-boarding a new user
|
|
125
|
+
KnowledgeBaseSearchTask(
|
|
126
|
+
instance=self.instance,
|
|
127
|
+
fixed_config={
|
|
128
|
+
"alternative_answers": [],
|
|
129
|
+
"item": f"{self.protocol_name}",
|
|
130
|
+
"question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
|
|
131
|
+
"value": "",
|
|
132
|
+
},
|
|
133
|
+
is_validated=False,
|
|
134
|
+
used_in_level_2=False,
|
|
135
|
+
),
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
create_user_subtask = [
|
|
139
|
+
# Navigate to the user list
|
|
140
|
+
AllMenuTask(
|
|
141
|
+
instance=self.instance,
|
|
142
|
+
fixed_config={
|
|
143
|
+
"application": "System Security",
|
|
144
|
+
"module": "Users and Groups > Users",
|
|
145
|
+
"url": "/now/nav/ui/classic/params/target/sys_user_list.do",
|
|
146
|
+
},
|
|
147
|
+
is_validated=False,
|
|
148
|
+
used_in_level_2=True,
|
|
149
|
+
),
|
|
150
|
+
# Create a new user
|
|
151
|
+
CreateUserTask(
|
|
152
|
+
instance=self.instance,
|
|
153
|
+
fixed_config=user_config,
|
|
154
|
+
is_validated=True,
|
|
155
|
+
used_in_level_2=True,
|
|
156
|
+
),
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
order_hardware_subtask = [
|
|
160
|
+
# Navigate to the hardware asset list
|
|
161
|
+
AllMenuTask(
|
|
162
|
+
instance=self.instance,
|
|
163
|
+
fixed_config={
|
|
164
|
+
"application": "Self-Service",
|
|
165
|
+
"module": "Service Catalog",
|
|
166
|
+
"url": "/now/nav/ui/classic/params/target/catalog_home.do",
|
|
167
|
+
},
|
|
168
|
+
is_validated=False,
|
|
169
|
+
used_in_level_2=True,
|
|
170
|
+
),
|
|
171
|
+
# Order a MacBook Pro 15
|
|
172
|
+
OrderAppleMacBookPro15Task(
|
|
173
|
+
instance=self.instance,
|
|
174
|
+
fixed_config={
|
|
175
|
+
"configuration": {},
|
|
176
|
+
"description": "Apple MacBook Pro",
|
|
177
|
+
"item": "Apple MacBook Pro 15",
|
|
178
|
+
"quantity": 1,
|
|
179
|
+
},
|
|
180
|
+
is_validated=True,
|
|
181
|
+
used_in_level_2=True,
|
|
182
|
+
),
|
|
183
|
+
]
|
|
184
|
+
# The unique name for the user is created once the task is instantiated
|
|
185
|
+
user_full_name = (
|
|
186
|
+
create_user_subtask[1].template_record["first_name"]
|
|
187
|
+
+ " "
|
|
188
|
+
+ create_user_subtask[1].template_record["last_name"]
|
|
189
|
+
)
|
|
190
|
+
# Set the assigned_to field in the hardware asset configuration to the user's email
|
|
191
|
+
hardware_config["template_record"]["assigned_to"] = user_full_name
|
|
192
|
+
if "assigned_to" not in hardware_config["task_fields"]:
|
|
193
|
+
hardware_config["task_fields"].append("assigned_to")
|
|
194
|
+
|
|
195
|
+
create_hardware_subtask = [
|
|
196
|
+
# Navigate to the hardware asset list
|
|
197
|
+
AllMenuTask(
|
|
198
|
+
instance=self.instance,
|
|
199
|
+
fixed_config={
|
|
200
|
+
"application": "Asset",
|
|
201
|
+
"module": "Portfolios > Hardware Assets",
|
|
202
|
+
"url": "/now/nav/ui/classic/params/target/alm_hardware_list.do",
|
|
203
|
+
},
|
|
204
|
+
is_validated=False,
|
|
205
|
+
used_in_level_2=True,
|
|
206
|
+
),
|
|
207
|
+
# Create a new hardware asset
|
|
208
|
+
CreateHardwareAssetTask(
|
|
209
|
+
instance=self.instance,
|
|
210
|
+
fixed_config=hardware_config,
|
|
211
|
+
is_validated=True,
|
|
212
|
+
used_in_level_2=True,
|
|
213
|
+
),
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
config = (
|
|
217
|
+
navigate_to_protocol_subtask
|
|
218
|
+
+ create_user_subtask
|
|
219
|
+
+ order_hardware_subtask
|
|
220
|
+
+ create_hardware_subtask
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return config
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
__TASKS__ = [OnBoardUserTask]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from playwright.sync_api import Page
|
|
2
|
+
from typing import List, Tuple
|
|
3
|
+
|
|
4
|
+
from ..base import AbstractServiceNowTask
|
|
5
|
+
from ..comp_building_block import CompositionalBuildingBlockTask
|
|
6
|
+
from ..utils.utils import check_url_suffix_match
|
|
7
|
+
from ..utils.private_tasks import create_private_task_and_get_sys_id
|
|
8
|
+
|
|
9
|
+
from ...api.utils import db_delete_from_table, table_api_call
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UpdatePrivateTask(AbstractServiceNowTask, CompositionalBuildingBlockTask):
|
|
13
|
+
"""
|
|
14
|
+
Set a private task to complete, assuming we start on the task viewed as form.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
-----------
|
|
18
|
+
instance: SNowInstance
|
|
19
|
+
The instance to use.
|
|
20
|
+
start_rel_url: str
|
|
21
|
+
The relative URL of the task list.
|
|
22
|
+
fixed_config: dict
|
|
23
|
+
Configuration to use for the task. If provided, the task will use the provided configuration instead of
|
|
24
|
+
selecting a random one. See browsergym/workarena/data_files/task_configs/filter_change_request_list_task.json
|
|
25
|
+
for an example of a configuration file.
|
|
26
|
+
set_as_completed: bool
|
|
27
|
+
Whether the task should be marked as complete or not. If True, the task will be marked as complete; otherwise, marked as.
|
|
28
|
+
used to set infeasible tasks to incomplete.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
seed: int = None,
|
|
34
|
+
instance=None,
|
|
35
|
+
start_rel_url="/now/nav/ui/classic/params/target/task_list.do%3Fsysparm_userpref_module%3D1523b8d4c611227b00be8216ec331b9a%26sysparm_query%3Dactive%253Dtrue%255Eassigned_to%253Djavascript%253AgetMyAssignments%2528%2529%255Estate%2521%253D-5%255EEQ",
|
|
36
|
+
fixed_config: dict = None,
|
|
37
|
+
set_as_completed: bool = True,
|
|
38
|
+
**kwargs,
|
|
39
|
+
) -> None:
|
|
40
|
+
super().__init__(seed=seed, instance=instance, start_rel_url=start_rel_url)
|
|
41
|
+
self.fixed_config = fixed_config
|
|
42
|
+
self.config = fixed_config
|
|
43
|
+
self.set_as_completed = set_as_completed
|
|
44
|
+
# 3 is the state for "Closed-Complete", 4 is "Closed-Incomplete", 7 is "Closed-Skipped"
|
|
45
|
+
self.allowed_options = ["3"] if self.set_as_completed else ["4", "7"]
|
|
46
|
+
self.private_task_id = "PTSK" + str(id(self) % (10**8)).zfill(8)
|
|
47
|
+
if self.fixed_config is None:
|
|
48
|
+
self.config = {
|
|
49
|
+
"task_description": "Close private task",
|
|
50
|
+
"short_description": self.private_task_id,
|
|
51
|
+
}
|
|
52
|
+
self.sys_id = None
|
|
53
|
+
self.task_rel_url = None # Relative URL of the task in form view
|
|
54
|
+
self.__dict__.update(kwargs)
|
|
55
|
+
|
|
56
|
+
def setup_goal(self, page: Page) -> tuple[str, dict]:
|
|
57
|
+
task_description = self.config["task_description"]
|
|
58
|
+
short_description = self.config["short_description"]
|
|
59
|
+
self.sys_id = create_private_task_and_get_sys_id(
|
|
60
|
+
self.instance,
|
|
61
|
+
page,
|
|
62
|
+
self.private_task_id,
|
|
63
|
+
task_description,
|
|
64
|
+
short_description,
|
|
65
|
+
user_sys_id=self._base_user_sysid,
|
|
66
|
+
)
|
|
67
|
+
self.task_rel_url = (
|
|
68
|
+
f"/now/nav/ui/classic/params/target/vtb_task.do%3Fsys_id%3D{self.sys_id}"
|
|
69
|
+
)
|
|
70
|
+
goal = f"Close private task {self.private_task_id}"
|
|
71
|
+
|
|
72
|
+
return goal, {}
|
|
73
|
+
|
|
74
|
+
def get_pretty_printed_description(self) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Get the task info for this task when used in a private task; Used in L3 compositional tasks.
|
|
77
|
+
called by subclasses
|
|
78
|
+
"""
|
|
79
|
+
task_info = "Don't forget to mark this task as complete once you're done."
|
|
80
|
+
|
|
81
|
+
return task_info
|
|
82
|
+
|
|
83
|
+
def cheat(self, page: Page, chat_messages: list[str]) -> None:
|
|
84
|
+
super().cheat(page, chat_messages)
|
|
85
|
+
frame = page.wait_for_selector('iframe[name="gsft_main"]').content_frame()
|
|
86
|
+
# Search for the private task by search for the number
|
|
87
|
+
frame.get_by_label("Search a specific field of the Tasks list").select_option("number")
|
|
88
|
+
search_input = frame.locator('input[aria-label="Search"]')
|
|
89
|
+
search_input.click()
|
|
90
|
+
search_input.fill(self.private_task_id)
|
|
91
|
+
search_input.press("Enter")
|
|
92
|
+
page.wait_for_timeout(1500)
|
|
93
|
+
# Click on the private task to open it
|
|
94
|
+
frame.get_by_label(f"Open record: {self.private_task_id}").click()
|
|
95
|
+
page.wait_for_timeout(2000)
|
|
96
|
+
page.wait_for_load_state("networkidle")
|
|
97
|
+
frame = page.wait_for_selector('iframe[name="gsft_main"]').content_frame()
|
|
98
|
+
page.wait_for_timeout(1500)
|
|
99
|
+
# Click on the task state, select "Closed-Complete" if complete, else "Closed Skipped" and update the task
|
|
100
|
+
option = "3" if self.set_as_completed else "7"
|
|
101
|
+
frame.get_by_label("state").first.select_option(option)
|
|
102
|
+
frame.get_by_text("update").first.click()
|
|
103
|
+
# Wait for record to be updated in the DB
|
|
104
|
+
record_updated = False
|
|
105
|
+
while not record_updated:
|
|
106
|
+
record = table_api_call(
|
|
107
|
+
instance=self.instance,
|
|
108
|
+
table="vtb_task",
|
|
109
|
+
params={"sysparm_query": f"task_effective_number={self.private_task_id}"},
|
|
110
|
+
)["result"]
|
|
111
|
+
record_updated = record[0]["state"] == option
|
|
112
|
+
page.wait_for_timeout(1000)
|
|
113
|
+
|
|
114
|
+
def validate(self, page: Page, chat_messages: list[str]) -> Tuple[float, bool, str, dict]:
|
|
115
|
+
"""
|
|
116
|
+
Validate the solution
|
|
117
|
+
"""
|
|
118
|
+
record = table_api_call(
|
|
119
|
+
instance=self.instance,
|
|
120
|
+
table="vtb_task",
|
|
121
|
+
params={"sysparm_query": f"task_effective_number={self.private_task_id}"},
|
|
122
|
+
)["result"]
|
|
123
|
+
if not record:
|
|
124
|
+
return 0, False, "", {"message": "Private task not found."}
|
|
125
|
+
if record[0]["state"] not in self.allowed_options:
|
|
126
|
+
return 0, False, "", {"message": "Private task not closed appropriately."}
|
|
127
|
+
|
|
128
|
+
return 1, True, "Nice work, thank you!", {"message": "Private task was closed."}
|
|
129
|
+
|
|
130
|
+
def teardown(self) -> None:
|
|
131
|
+
record_exists = table_api_call(
|
|
132
|
+
instance=self.instance,
|
|
133
|
+
table="vtb_task",
|
|
134
|
+
params={"sysparm_query": f"sys_id={self.sys_id}"},
|
|
135
|
+
)["result"]
|
|
136
|
+
if record_exists:
|
|
137
|
+
db_delete_from_table(
|
|
138
|
+
instance=self.instance,
|
|
139
|
+
table="vtb_task",
|
|
140
|
+
sys_id=self.sys_id,
|
|
141
|
+
)
|
|
142
|
+
super().teardown()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
__TASKS__ = [UpdatePrivateTask]
|