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
@@ -0,0 +1,396 @@
1
+ import faker
2
+
3
+ fake = faker.Faker()
4
+
5
+ from playwright.sync_api._generated import Page
6
+
7
+ from .base import CompositionalTask, HumanEvalTask
8
+
9
+ from ..base import AbstractServiceNowTask
10
+ from ..dashboard import WorkLoadBalancingMinMaxRetrievalTask
11
+ from ..form import EditProblemTask
12
+ from ..knowledge import KnowledgeBaseSearchTask
13
+ from ..list import FilterProblemListForWorkLoadBalancingTask
14
+ from ..navigation import AllMenuTask
15
+ from ..send_chat_message import SendChatMessageGenericTask
16
+
17
+ from ...api.problem import create_problem
18
+ from ...api.report import create_report
19
+ from ...api.user import create_user
20
+ from ...api.utils import db_delete_from_table, table_api_call
21
+ from ...instance import SNowInstance
22
+
23
+
24
+ class WorkloadBalancingTask(CompositionalTask):
25
+ def __init__(
26
+ self,
27
+ seed: int = None,
28
+ instance: SNowInstance = None,
29
+ fixed_config: list[AbstractServiceNowTask] = None,
30
+ level: int = 2,
31
+ min_users: int = 2,
32
+ max_users: int = 4,
33
+ # Ranges to randomly choose from
34
+ max_problem_range: int = [3, 4],
35
+ mid_problem_range: int = [2, 3],
36
+ min_problem_range: int = [1, 2],
37
+ ) -> None:
38
+ """
39
+ Workload balancing task:
40
+ - Navigate to the KB
41
+ - Find the protocol for re-distributing work
42
+ - Find the user who has the greatest number of problems assigned to them
43
+ - Re-assign the problems to the user having the least number of problems assigned to them
44
+
45
+ Parameters:
46
+ -----------
47
+ instance: SNowInstance
48
+ The ServiceNow instance to run the task on.
49
+ fixed_config: list[AbstractServiceNowTask]
50
+ A list of subtasks.
51
+ level: int
52
+ 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.
53
+ L3 will start in a private task page describing the information needed to complete the task and the related company protocol
54
+ to complete it.
55
+ min_users: int
56
+ The minimum number of users to create and to distribute the problems to
57
+ max_users: int
58
+ The maximum number of users to create and to distribute the problems to
59
+ max_problem_range: list[int, int]
60
+ The range of the number of problems to assign to the user with the most problems
61
+ mid_problem_range: list[int, int]
62
+ The range of the number of problems to assign to all users but the ones with the least/most problems
63
+ min_problem_range: list[int, int]
64
+ The range of the number of problems to assign to the user with the least problems
65
+ Attributes:
66
+ -----------
67
+ task_description: str
68
+ The start of the task description to be completed. e.g. "Referring to company protocol 'Agent Workload Balancing', re-distribute the problems with description containing {self.problem_hashtag}"
69
+ short_description: str
70
+ A short description of the task to be completed. e.g. "Balance the workload for problems with description containing {self.problem_hashtag}"
71
+ """
72
+ assert level in [2, 3], "Level must be either 2 or 3"
73
+ self.level = level
74
+ self.protocol_name = "Agent Workload Balancing"
75
+ super().__init__(
76
+ seed=seed,
77
+ instance=instance,
78
+ fixed_config=fixed_config,
79
+ level=level,
80
+ protocol_name=self.protocol_name,
81
+ )
82
+
83
+ self.problem_hashtag = (
84
+ f"#PRB{str(id(self) % (10**8)).zfill(9)}" # identifier to select problems
85
+ )
86
+ self.task_description = None
87
+ self.short_description = None
88
+ self.min_users = min_users
89
+ self.max_users = max_users
90
+ self.max_problem_range = max_problem_range
91
+ self.mid_problem_range = mid_problem_range
92
+ self.min_problem_range = min_problem_range
93
+
94
+ # In this case, there will only be 2 users as the values are bound and the top value is excluded in the randint function
95
+ if self.min_users == 2 and self.max_users == 3:
96
+ assert (
97
+ self.min_problem_range[1] <= self.max_problem_range[0]
98
+ ), "The problem ranges should not overlap"
99
+ # In this case, there will be 3 users
100
+ else:
101
+ assert (
102
+ self.min_problem_range[1] <= self.mid_problem_range[0]
103
+ and self.mid_problem_range[1] <= self.max_problem_range[0]
104
+ ), "The problem ranges should not overlap"
105
+ assert self.max_problem_range[1] <= 6, "The maximum number of problems should not exceed 6"
106
+
107
+ self.plot_title = None # The title of the plot created for the report
108
+ self.lowest_priority = 0 # The lowest priority of the problems; a high number indicates a low priority. Set in the setup_goal method
109
+
110
+ self.category_name = (
111
+ fake.word() + "-" + fake.word()
112
+ ) # The category of the problems to re-distribute
113
+ self.category_sys_id = (
114
+ None # The sys_id of the category created for the task; create in the setup_goal method
115
+ )
116
+ self.user_sys_ids = [] # The sys_ids of the users created for the task
117
+ self.problem_sys_ids = [] # The sys_ids of the problems created for the task
118
+ self.report_sys_id = None # The sys_id of the report created for the task
119
+ self.user_with_most_problems = None # The name of the user that has the most problems assigned; defined in the setup_goal method
120
+ self.user_with_least_problems = None # The name of the user that has the least problems assigned; defined in the setup_goal method
121
+ self.problem_to_edit_sys_id = (
122
+ None # The sys_id of the problem to re-assign; defined in the setup_goal method
123
+ )
124
+ self.problem_to_edit_number = (
125
+ None # The number of the problem to re-assign; defined in the setup_goal method
126
+ )
127
+
128
+ def setup_goal(self, page: Page) -> tuple[str, dict]:
129
+ num_users = self.random.randint(self.min_users, self.max_users)
130
+ max_problems = self.random.randint(*self.max_problem_range)
131
+ min_problems = self.random.randint(*self.min_problem_range)
132
+
133
+ # Create users, create problems and assign problems to users
134
+ for i in range(num_users):
135
+ if i == 0:
136
+ num_problems = max_problems
137
+ elif i == num_users - 1:
138
+ num_problems = min_problems
139
+ else:
140
+ num_problems = self.random.randint(*self.mid_problem_range)
141
+ first_name = fake.first_name() + "-" + fake.first_name()
142
+ last_name = fake.last_name() + "-" + fake.last_name()
143
+ user_full_name = first_name + " " + last_name
144
+ _, _, user_sys_id = create_user(
145
+ instance=self.instance,
146
+ first_name=first_name,
147
+ last_name=last_name,
148
+ user_roles=[
149
+ "admin",
150
+ "problem_admin",
151
+ "problem_manager",
152
+ "problem_coordinator",
153
+ "problem_task_analyst",
154
+ ],
155
+ random=self.random,
156
+ )
157
+ self.user_sys_ids.append(user_sys_id)
158
+
159
+ if i == 0:
160
+ self.user_with_most_problems = user_full_name
161
+ elif i == num_users - 1:
162
+ self.user_with_least_problems = user_full_name
163
+
164
+ # Create problems assigned to current user
165
+ for j in range(num_problems):
166
+ # Assign a priority to the problem; 1 being highest priority and 5 being lowest
167
+ # the use of j % 5 is to ensure that the priority is between 1 and 5 and that there is
168
+ # only one problem with the lowest priority
169
+ priority = (j % 5) + 1
170
+ self.lowest_priority = max(self.lowest_priority, priority)
171
+ problem_sys_id, problem_number = create_problem(
172
+ instance=self.instance,
173
+ user_sys_id=user_sys_id,
174
+ priority=priority,
175
+ problem_hashtag=self.problem_hashtag,
176
+ return_number=True,
177
+ )
178
+ # The last problem created is the one to re-assign as it will be the one with the lowest priority (highest priority value)
179
+ # and the first user will be the one with the most problems assigned
180
+ if i == 0 and j == num_problems - 1:
181
+ self.problem_to_edit_sys_id = problem_sys_id
182
+ self.problem_to_edit_number = problem_number
183
+ self.problem_sys_ids.append(problem_sys_id)
184
+
185
+ # Create a report for problems of the current category
186
+ self.report_sys_id, plot_title = create_report(
187
+ instance=self.instance,
188
+ table="problem",
189
+ filter_hashtag=self.problem_hashtag,
190
+ field="assigned_to",
191
+ plot_title=f"Problems for with hashtag {self.problem_hashtag}",
192
+ random=self.random,
193
+ )
194
+ self.plot_title = plot_title
195
+
196
+ # Sample a configuration
197
+ config = self._get_config()
198
+ # Get the task description
199
+ self.short_description = (
200
+ f"Balance the workload for problems with hashtag {self.problem_hashtag}"
201
+ )
202
+ self.task_description = f"Referring to company protocol '{self.protocol_name}' (located in the \"Company Protocols\" knowledge base) re-distribute the problems with hashtag={self.problem_hashtag}."
203
+
204
+ goal, info = super().setup_goal(page=page, config=config)
205
+
206
+ return goal, info
207
+
208
+ def _get_config(self) -> list[AbstractServiceNowTask]:
209
+ """ """
210
+ navigate_to_protocol_subtask = [
211
+ # Navigate to the KB
212
+ AllMenuTask(
213
+ instance=self.instance,
214
+ fixed_config={
215
+ "application": "Self-Service",
216
+ "module": "Knowledge",
217
+ "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
218
+ },
219
+ is_validated=False,
220
+ used_in_level_2=False,
221
+ ),
222
+ # Find the protocol for on-boarding a new user
223
+ KnowledgeBaseSearchTask(
224
+ instance=self.instance,
225
+ fixed_config={
226
+ "alternative_answers": [],
227
+ "item": f"{self.protocol_name}",
228
+ "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
229
+ "value": "",
230
+ },
231
+ is_validated=False,
232
+ used_in_level_2=False,
233
+ ),
234
+ ]
235
+
236
+ find_most_and_least_busy_users_subtask = [
237
+ # Navigate to the reports list
238
+ AllMenuTask(
239
+ instance=self.instance,
240
+ fixed_config={
241
+ "application": "Reports",
242
+ "module": "Administration > All",
243
+ "url": "/now/nav/ui/classic/params/target/sys_report_list.do",
244
+ },
245
+ is_validated=False,
246
+ used_in_level_2=True,
247
+ ),
248
+ WorkLoadBalancingMinMaxRetrievalTask(
249
+ instance=self.instance,
250
+ fixed_config={
251
+ "url": "/now/nav/ui/classic/params/target/sys_report",
252
+ "chart_title": self.plot_title,
253
+ "chart_series": "",
254
+ "question": "min",
255
+ },
256
+ is_validated=False,
257
+ used_in_level_2=True,
258
+ problem_hashtag=self.problem_hashtag,
259
+ ),
260
+ ]
261
+
262
+ reassign_problem_subtask = [
263
+ # Navigate to the hardware asset list
264
+ AllMenuTask(
265
+ instance=self.instance,
266
+ fixed_config={
267
+ "application": "Problem",
268
+ "module": "All",
269
+ "url": "/now/nav/ui/classic/params/target/problem_list.do",
270
+ },
271
+ is_validated=False,
272
+ used_in_level_2=True,
273
+ ),
274
+ # Filter the problems by assignee, and priority = lowest priority
275
+ # The existence of a lower priority problem is guaranteed
276
+ FilterProblemListForWorkLoadBalancingTask(
277
+ instance=self.instance,
278
+ fixed_config={
279
+ "filter_columns": ["assigned_to", "priority"],
280
+ "filter_kind": "AND",
281
+ "filter_values": [
282
+ f"{self.user_with_most_problems}",
283
+ f"{self.lowest_priority}",
284
+ ],
285
+ },
286
+ is_validated=False,
287
+ used_in_level_2=True,
288
+ goal=f'Create a filter to find problems where \n - "Assigned to" is the user with the most problems assigned and \n - "Priority" is "{self.lowest_priority}".',
289
+ ),
290
+ # Assign a problem to the user with the least problems assigned to them
291
+ EditProblemTask(
292
+ instance=self.instance,
293
+ new_values={"assigned_to": f"{self.user_with_least_problems}"},
294
+ record_sys_id=self.problem_to_edit_sys_id,
295
+ record_number=self.problem_to_edit_number,
296
+ is_validated=True,
297
+ used_in_level_2=True,
298
+ level=self.level,
299
+ ),
300
+ ]
301
+
302
+ config = (
303
+ navigate_to_protocol_subtask
304
+ + find_most_and_least_busy_users_subtask
305
+ + reassign_problem_subtask
306
+ )
307
+
308
+ return config
309
+
310
+ def teardown(self) -> None:
311
+ # Delete the users
312
+ for user_sys_id in self.user_sys_ids:
313
+ record_exists = table_api_call(
314
+ instance=self.instance,
315
+ table="sys_user",
316
+ params={"sysparm_query": f"sys_id={user_sys_id}"},
317
+ )
318
+ if record_exists:
319
+ db_delete_from_table(
320
+ instance=self.instance,
321
+ table="sys_user",
322
+ sys_id=user_sys_id,
323
+ )
324
+ # Delete the problems
325
+ for problem_sys_id in self.problem_sys_ids:
326
+ record_exists = table_api_call(
327
+ instance=self.instance,
328
+ table="problem",
329
+ params={"sysparm_query": f"sys_id={problem_sys_id}"},
330
+ )
331
+ if record_exists:
332
+ db_delete_from_table(
333
+ instance=self.instance,
334
+ table="problem",
335
+ sys_id=problem_sys_id,
336
+ )
337
+ # Delete the report
338
+ db_delete_from_table(
339
+ instance=self.instance,
340
+ table="sys_report",
341
+ sys_id=self.report_sys_id,
342
+ )
343
+ super().teardown()
344
+
345
+
346
+ class WorkloadBalancingSmallTask(WorkloadBalancingTask, HumanEvalTask):
347
+ def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
348
+ super().__init__(
349
+ seed=seed,
350
+ instance=instance,
351
+ level=level,
352
+ min_users=2,
353
+ max_users=4,
354
+ max_problem_range=[4, 6],
355
+ mid_problem_range=[3, 4],
356
+ min_problem_range=[1, 3],
357
+ )
358
+
359
+
360
+ class WorkloadBalancingMediumTask(WorkloadBalancingTask):
361
+ def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
362
+ super().__init__(
363
+ seed=seed,
364
+ instance=instance,
365
+ level=level,
366
+ min_users=5,
367
+ max_users=7,
368
+ max_problem_range=[5, 6],
369
+ mid_problem_range=[3, 5],
370
+ min_problem_range=[1, 3],
371
+ )
372
+
373
+
374
+ class WorkloadBalancingLargeTask(WorkloadBalancingTask):
375
+ def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
376
+ super().__init__(
377
+ seed=seed,
378
+ instance=instance,
379
+ level=level,
380
+ min_users=8,
381
+ max_users=10,
382
+ max_problem_range=[5, 6],
383
+ mid_problem_range=[3, 5],
384
+ min_problem_range=[1, 3],
385
+ )
386
+
387
+
388
+ local_vars = locals().copy()
389
+
390
+ __TASKS__ = [
391
+ var
392
+ for var in local_vars.values()
393
+ if isinstance(var, type)
394
+ and issubclass(var, WorkloadBalancingTask)
395
+ and var is not WorkloadBalancingTask
396
+ ]