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,457 @@
1
+ import html
2
+ import json
3
+ import random
4
+
5
+ from faker import Faker
6
+ from playwright.sync_api._generated import Page
7
+ from typing import Tuple
8
+
9
+ fake = Faker()
10
+
11
+ from ...api.knowledge import give_kb_read_permissions
12
+ from ...api.utils import table_api_call
13
+ from ..base import AbstractServiceNowTask
14
+ from .base import CompositionalTask
15
+ from ...config import KB_FILEPATH, PROTOCOL_KB_NAME
16
+ from ...instance import SNowInstance
17
+ from ..knowledge import KnowledgeBaseSearchTask, AddCommentToKnowledgeArticleTask
18
+ from ..navigation import AllMenuTask
19
+
20
+
21
+ class EditKnowledgeBaseTask(CompositionalTask):
22
+ def __init__(
23
+ self,
24
+ seed: int = None,
25
+ instance: SNowInstance = None,
26
+ fixed_config: list[AbstractServiceNowTask] = None,
27
+ level: int = 3,
28
+ ) -> None:
29
+ """
30
+ Create a compositional task with specific subtasks
31
+
32
+ Parameters:
33
+ -----------
34
+ instance: SNowInstance
35
+ The ServiceNow instance to run the task on.
36
+ fixed_config: list[AbstractServiceNowTask]
37
+ A list of tuples, each containing a subtask, its configuration and whether or not it should be validated.
38
+ level: int
39
+ 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.
40
+ L3 will start in a private task page describing the information needed to complete the task and the related company protocol
41
+ to complete it.
42
+
43
+ Attributes:
44
+ -----------
45
+ task_description: str
46
+ The start of the task description to be completed. e.g. "Referring to company protocol 'Edit a knowledge article', edit the knowledge base to handle the incorrect information: \n'
47
+ short_description: str
48
+ A short description of the task to be completed. e.g. "Edit knowledge base entries for address of parking lot."
49
+ """
50
+ assert level in [2, 3], "Level must be either 2 or 3"
51
+ self.level = level
52
+ self.protocol_name = "Edit a knowledge article to manage incorrect information"
53
+
54
+ super().__init__(
55
+ seed=seed,
56
+ instance=instance,
57
+ fixed_config=fixed_config,
58
+ level=level,
59
+ protocol_name=self.protocol_name,
60
+ user_roles=["itil"], # Required permission to access service desk for l3
61
+ )
62
+ with open(KB_FILEPATH, "r") as f:
63
+ self.kb_entries = json.load(f)
64
+ if not hasattr(self, "_base_initial_instance"):
65
+ self._base_initial_instance = self.instance
66
+ self.adhoc_kb_name = None
67
+ self.task_description = None
68
+ self.short_description = None
69
+
70
+ def create_adhoc_kb(self):
71
+ user_full_name = " ".join(self._base_user_name.split(".")[:-1])
72
+ adhoc_kb_name = f"{user_full_name}'s Knowledge Base"
73
+ self.adhoc_kb_name = adhoc_kb_name
74
+
75
+ kb = table_api_call(
76
+ instance=self._base_initial_instance,
77
+ table="kb_knowledge_base",
78
+ method="POST",
79
+ data=json.dumps(
80
+ {
81
+ "title": self.adhoc_kb_name,
82
+ }
83
+ ),
84
+ )["result"]
85
+
86
+ return kb["sys_id"]
87
+
88
+ def get_random_article_name(self):
89
+ kb = table_api_call(
90
+ instance=self.instance,
91
+ table="kb_knowledge",
92
+ params={
93
+ "sysparm_query": f"kb_knowledge_base={self.adhoc_kb_sys_id}",
94
+ "sysparm_fields": "short_description",
95
+ },
96
+ )["result"]
97
+ self.article_titles = [kb_article["short_description"] for kb_article in kb]
98
+
99
+ article_date = fake.date_this_year().strftime("%Y-%m-%d")
100
+ base_article_name = self.base_config["item"].capitalize()
101
+ article_title = f"{base_article_name}-{article_date}"
102
+ while article_title in self.article_titles:
103
+ article_date = fake.date_this_year().strftime("%Y-%m-%d")
104
+ article_title = f"{base_article_name}-{article_date}"
105
+
106
+ return article_title
107
+
108
+ def create_article(self, article_name, article_text):
109
+ if article_name in self.article_titles:
110
+ raise Exception("Article with the name already exists...")
111
+
112
+ adhoc_kb_response = table_api_call(
113
+ instance=self._base_initial_instance, # admin permissions to contribute to the KB
114
+ table="kb_knowledge",
115
+ method="POST",
116
+ data=json.dumps(
117
+ {
118
+ "short_description": article_name,
119
+ "sys_class_name": "kb_knowledge",
120
+ "text": article_text,
121
+ "article_type": "text",
122
+ "kb_knowledge_base": self.adhoc_kb_sys_id,
123
+ }
124
+ ),
125
+ )["result"]
126
+
127
+ return adhoc_kb_response
128
+
129
+ def setup_goal(self, page: Page) -> tuple[str, dict]:
130
+ # Create the KB
131
+ self.adhoc_kb_sys_id = self.create_adhoc_kb()
132
+ # Sample a configuration
133
+ self.base_config = self.random.choice(self.kb_entries)
134
+ self.incorrect_kb_article_name = self.get_random_article_name()
135
+ self.correct_kb_article_name = self.get_random_article_name()
136
+ self.item = self.base_config["item"]
137
+ self.correct_answer = self.base_config["value"]
138
+
139
+ self.incorrect_answer = " ".join(
140
+ [fake.word() for _ in range(len(self.correct_answer.split()))]
141
+ ) # Random incorrect answer with the same number of words as the correct answer
142
+
143
+ incorrect_kb_article = self.create_article(
144
+ self.incorrect_kb_article_name,
145
+ self.base_config["article"].replace(self.correct_answer, self.incorrect_answer),
146
+ )
147
+ self.incorrect_kb_article_sys_id = incorrect_kb_article["sys_id"]
148
+ self.incorrect_kb_article_number = incorrect_kb_article["number"]
149
+
150
+ correct_kb_article = self.create_article(
151
+ self.correct_kb_article_name, self.base_config["article"]
152
+ )
153
+ self.correct_kb_article_sys_id = correct_kb_article["sys_id"]
154
+ self.correct_kb_article_number = correct_kb_article["number"]
155
+
156
+ config = self.fixed_config if self.fixed_config else self._get_config()
157
+
158
+ give_kb_read_permissions(
159
+ self._base_initial_instance,
160
+ self._base_user_sysid,
161
+ self._base_user_name,
162
+ self.adhoc_kb_sys_id,
163
+ self.adhoc_kb_name,
164
+ )
165
+
166
+ if self.level == 3:
167
+ protocol_kb_sys_id = table_api_call(
168
+ instance=self._base_initial_instance,
169
+ table="kb_knowledge_base",
170
+ params={"sysparm_query": f"title={PROTOCOL_KB_NAME}"},
171
+ )["result"][0]["sys_id"]
172
+ give_kb_read_permissions(
173
+ self._base_initial_instance,
174
+ self._base_user_sysid,
175
+ self._base_user_name,
176
+ protocol_kb_sys_id,
177
+ PROTOCOL_KB_NAME,
178
+ )
179
+
180
+ # Get the task description
181
+ self.short_description = f"Edit knowledge base article for {self.item}"
182
+ self.task_description = (
183
+ f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) edit the knowledge base to handle incorrect information. \n'
184
+ + f'Searching for "{self.item}" in the knowledge base gives different articles as the output: "{self.incorrect_kb_article_name}" with number "{self.incorrect_kb_article_number}" and "{self.correct_kb_article_name}" with number "{self.correct_kb_article_number}". \n'
185
+ # + f'One of the articles has incorrect information "{self.incorrect_answer}" and the other one has the correct answer "{self.correct_answer}". \n'
186
+ + f'The correct information for "{self.item}" should be {self.correct_answer}. '
187
+ )
188
+
189
+ goal, info = super().setup_goal(page=page, config=config)
190
+
191
+ return goal, info
192
+
193
+ def _get_config(self) -> list[tuple[AbstractServiceNowTask, dict, bool]]:
194
+ """Add more extensive definition here."""
195
+
196
+ self.incorrect_config = {
197
+ "item": self.item,
198
+ "kb_article_title": self.incorrect_kb_article_name,
199
+ "value": self.incorrect_answer,
200
+ "question": self.base_config["questions"][0],
201
+ # "replaced_text": self.incorrect_answer,
202
+ "comment": f"This article has incorrect information and is obsolete. Please refer to the article numbered {self.correct_kb_article_number} for reference.",
203
+ "alternative_answers": [
204
+ self.incorrect_answer,
205
+ ],
206
+ }
207
+
208
+ self.correct_config = {
209
+ "item": self.item,
210
+ "kb_article_title": self.correct_kb_article_name,
211
+ "value": self.correct_answer,
212
+ "question": self.base_config["questions"][0],
213
+ "comment": f"This article has correct information. Please DO NOT refer to the article numbered {self.incorrect_kb_article_number} for reference.",
214
+ "alternative_answers": [
215
+ self.correct_answer,
216
+ ],
217
+ }
218
+
219
+ navigate_to_protocol_subtask = [
220
+ # Navigate to the KB
221
+ AllMenuTask(
222
+ instance=self.instance,
223
+ fixed_config={
224
+ "application": "Self-Service",
225
+ "module": "Knowledge",
226
+ "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
227
+ },
228
+ is_validated=False,
229
+ used_in_level_2=False,
230
+ ),
231
+ # Find the protocol for on-boarding a new user
232
+ KnowledgeBaseSearchTask(
233
+ instance=self.instance,
234
+ fixed_config={
235
+ "alternative_answers": [],
236
+ "item": f"{self.protocol_name}",
237
+ "question": 'Can you find the "Edit Knowledge Article Protocol" in the Knowledge Base?',
238
+ "value": "",
239
+ },
240
+ is_validated=False,
241
+ used_in_level_2=False,
242
+ ),
243
+ ]
244
+
245
+ search_and_comment_knowledge_base_incorrect_subtask = [
246
+ # Navigate to the knowledge base home page
247
+ AllMenuTask(
248
+ instance=self.instance,
249
+ fixed_config={
250
+ "application": "Self-Service",
251
+ "module": "Knowledge",
252
+ "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
253
+ },
254
+ is_validated=False,
255
+ used_in_level_2=True,
256
+ ),
257
+ KnowledgeBaseSearchTask(
258
+ instance=self.instance,
259
+ fixed_config=self.incorrect_config,
260
+ is_validated=False,
261
+ used_in_level_2=True,
262
+ is_correct=False,
263
+ is_only_navigating=True,
264
+ search_by_title=True,
265
+ ),
266
+ # Search the knowledge base for the incorrect article
267
+ AddCommentToKnowledgeArticleTask(
268
+ instance=self.instance,
269
+ fixed_config=self.incorrect_config,
270
+ is_validated=False,
271
+ used_in_level_2=True,
272
+ ),
273
+ ]
274
+
275
+ search_and_comment_knowledge_base_correct_subtask = [
276
+ # Navigate to the knowledge base home page
277
+ AllMenuTask(
278
+ instance=self.instance,
279
+ fixed_config={
280
+ "application": "Self-Service",
281
+ "module": "Knowledge",
282
+ "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
283
+ },
284
+ is_validated=False,
285
+ used_in_level_2=True,
286
+ ),
287
+ KnowledgeBaseSearchTask(
288
+ instance=self.instance,
289
+ fixed_config=self.correct_config,
290
+ is_validated=False,
291
+ used_in_level_2=True,
292
+ is_only_navigating=True,
293
+ search_by_title=True,
294
+ ),
295
+ # Search the knowledge base for the incorrect article
296
+ AddCommentToKnowledgeArticleTask(
297
+ instance=self.instance,
298
+ fixed_config=self.correct_config,
299
+ is_validated=False,
300
+ used_in_level_2=True,
301
+ ),
302
+ ]
303
+
304
+ config = (
305
+ navigate_to_protocol_subtask
306
+ + search_and_comment_knowledge_base_incorrect_subtask
307
+ + search_and_comment_knowledge_base_correct_subtask
308
+ )
309
+
310
+ return config
311
+
312
+ def validate(self, page: Page, chat_messages: list[str]) -> Tuple[float, bool, str, dict]:
313
+ incorrect_article_kb_sys_id = table_api_call(
314
+ instance=self.instance,
315
+ table="kb_knowledge",
316
+ params={
317
+ "sysparm_query": f"short_description={self.incorrect_kb_article_name}",
318
+ },
319
+ )["result"][0]["sys_id"]
320
+
321
+ incorrect_synonyms = [
322
+ "incorrect",
323
+ "wrong",
324
+ "false",
325
+ "inaccurate",
326
+ "mistaken",
327
+ "erroneous",
328
+ "improper",
329
+ "invalid",
330
+ "untrue",
331
+ "misleading",
332
+ "off",
333
+ "obsolete",
334
+ ]
335
+ incorrect_validated = 0
336
+ all_comments = table_api_call(
337
+ instance=self.instance,
338
+ table="kb_feedback",
339
+ params={
340
+ "sysparm_query": f"article={incorrect_article_kb_sys_id}",
341
+ },
342
+ )["result"]
343
+
344
+ for comment in all_comments:
345
+ if (
346
+ any(
347
+ incorrect_synonym.lower() in html.unescape(comment["comments"]).lower()
348
+ for incorrect_synonym in incorrect_synonyms
349
+ )
350
+ and comment["sys_created_by"] == self._base_user_name
351
+ and self.correct_kb_article_number.lower()
352
+ in html.unescape(comment["comments"]).lower()
353
+ ):
354
+ incorrect_validated = 1
355
+ break
356
+
357
+ correct_article_kb_sys_id = table_api_call(
358
+ instance=self.instance,
359
+ table="kb_knowledge",
360
+ params={
361
+ "sysparm_query": f"short_description={self.correct_kb_article_name}",
362
+ },
363
+ )["result"][0]["sys_id"]
364
+
365
+ correct_synonyms = [
366
+ "correct",
367
+ "right",
368
+ "accurate",
369
+ "true",
370
+ "exact",
371
+ "precise",
372
+ "proper",
373
+ "valid",
374
+ "factual",
375
+ "appropriate",
376
+ "verifiable",
377
+ "up-to-date",
378
+ "up to date",
379
+ ]
380
+ correct_validated = 0
381
+ all_comments = table_api_call(
382
+ instance=self.instance,
383
+ table="kb_feedback",
384
+ params={
385
+ "sysparm_query": f"article={correct_article_kb_sys_id}",
386
+ },
387
+ )["result"]
388
+
389
+ for comment in all_comments:
390
+ if (
391
+ any(
392
+ correct_synonym.lower() in html.unescape(comment["comments"]).lower()
393
+ for correct_synonym in correct_synonyms
394
+ )
395
+ and comment["sys_created_by"] == self._base_user_name
396
+ and self.incorrect_kb_article_number.lower()
397
+ in html.unescape(comment["comments"]).lower()
398
+ ):
399
+ correct_validated = 1
400
+ break
401
+
402
+ if incorrect_validated and correct_validated:
403
+ # Validate final_l3 tasks
404
+ reward, done, message, info = super().validate(page, chat_messages)
405
+ return reward, done, message, info
406
+ elif incorrect_validated and not correct_validated:
407
+ return (
408
+ 0,
409
+ False,
410
+ "",
411
+ {
412
+ "message": "Comment successfully added to the incorrect article but not the correct article."
413
+ },
414
+ )
415
+ elif not incorrect_validated and correct_validated:
416
+ return (
417
+ 0,
418
+ False,
419
+ "",
420
+ {
421
+ "message": "Comment successfully added to the correct article but not the incorrect article."
422
+ },
423
+ )
424
+ else:
425
+ return (
426
+ 0,
427
+ False,
428
+ "",
429
+ {
430
+ "message": "Comment not added to either the correct article or the incorrect article."
431
+ },
432
+ )
433
+
434
+ def teardown(self) -> None:
435
+ # Delete created articles
436
+ table_api_call(
437
+ instance=self._base_initial_instance,
438
+ table=f"kb_knowledge/{self.incorrect_kb_article_sys_id}",
439
+ method="DELETE",
440
+ )
441
+ table_api_call(
442
+ instance=self._base_initial_instance,
443
+ table=f"kb_knowledge/{self.correct_kb_article_sys_id}",
444
+ method="DELETE",
445
+ )
446
+
447
+ # Archive knowledge base
448
+ table_api_call(
449
+ instance=self._base_initial_instance,
450
+ table=f"kb_knowledge_base/{self.adhoc_kb_sys_id}",
451
+ method="PATCH",
452
+ json={"title": f"archived_{self.adhoc_kb_sys_id}", "active": "false"},
453
+ )
454
+ return super().teardown()
455
+
456
+
457
+ __TASKS__ = [EditKnowledgeBaseTask]