Skip to content

Commit c913715

Browse files
committed
add update_question()
1 parent 35f2056 commit c913715

3 files changed

Lines changed: 370 additions & 0 deletions

File tree

examples/08_questions_management.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,197 @@ def get_question_details_example(j1):
437437
print(f"Error type: {type(e).__name__}")
438438
print(f"Error details: {str(e)}")
439439

440+
def update_question_examples(j1):
441+
"""Demonstrate updating existing questions."""
442+
443+
print("=== Update Question Examples ===\n")
444+
445+
# First, let's get a list of questions to find one to update
446+
print("1. Finding a question to update:")
447+
try:
448+
questions = j1.list_questions()
449+
450+
if questions:
451+
# Get the first question for demonstration
452+
question_to_update = questions[0]
453+
question_id = question_to_update['id']
454+
question_title = question_to_update['title']
455+
456+
print(f" Found question: {question_title}")
457+
print(f" Question ID: {question_id}")
458+
print(f" Current tags: {', '.join(question_to_update.get('tags', []))}")
459+
print(f" Current description: {question_to_update.get('description', 'No description')[:50]}...")
460+
print()
461+
462+
# Example 1: Update title and description
463+
print("2. Updating question title and description:")
464+
try:
465+
updated_question = j1.update_question(
466+
question_id=question_id,
467+
title=f"{question_title} - UPDATED",
468+
description="This question has been updated with new information and improved clarity."
469+
)
470+
471+
print(f" ✅ Successfully updated question!")
472+
print(f" New title: {updated_question['title']}")
473+
print(f" New description: {updated_question['description']}")
474+
print()
475+
476+
except Exception as e:
477+
print(f" ❌ Error updating title/description: {e}\n")
478+
479+
# Example 2: Update tags
480+
print("3. Updating question tags:")
481+
try:
482+
current_tags = question_to_update.get('tags', [])
483+
new_tags = current_tags + ["updated", "maintained", "reviewed"]
484+
485+
updated_question = j1.update_question(
486+
question_id=question_id,
487+
tags=new_tags
488+
)
489+
490+
print(f" ✅ Successfully updated tags!")
491+
print(f" New tags: {', '.join(updated_question['tags'])}")
492+
print()
493+
494+
except Exception as e:
495+
print(f" ❌ Error updating tags: {e}\n")
496+
497+
# Example 3: Update queries
498+
print("4. Updating question queries:")
499+
try:
500+
current_queries = question_to_update.get('queries', [])
501+
if current_queries:
502+
# Update the first query with improved version
503+
updated_queries = current_queries.copy()
504+
if len(updated_queries) > 0:
505+
updated_queries[0] = {
506+
**updated_queries[0],
507+
"query": f"{updated_queries[0].get('query', '')} LIMIT 100",
508+
"resultsAre": "INFORMATIVE"
509+
}
510+
511+
updated_question = j1.update_question(
512+
question_id=question_id,
513+
queries=updated_queries
514+
)
515+
516+
print(f" ✅ Successfully updated queries!")
517+
print(f" Number of queries: {len(updated_question['queries'])}")
518+
print(f" First query updated: {updated_question['queries'][0]['query'][:50]}...")
519+
print()
520+
else:
521+
print(" ⚠️ No queries found to update")
522+
print()
523+
524+
except Exception as e:
525+
print(f" ❌ Error updating queries: {e}\n")
526+
527+
# Example 4: Comprehensive update
528+
print("5. Comprehensive question update:")
529+
try:
530+
comprehensive_update = j1.update_question(
531+
question_id=question_id,
532+
title="Comprehensive Security Audit Question - UPDATED",
533+
description="This question has been comprehensively updated to include multiple security checks and improved query performance.",
534+
tags=["security", "audit", "comprehensive", "updated", "maintained"],
535+
showTrend=True,
536+
pollingInterval="ONE_DAY"
537+
)
538+
539+
print(f" ✅ Successfully completed comprehensive update!")
540+
print(f" Final title: {comprehensive_update['title']}")
541+
print(f" Final tags: {', '.join(comprehensive_update['tags'])}")
542+
print(f" Show trend: {comprehensive_update.get('showTrend', False)}")
543+
print(f" Polling interval: {comprehensive_update.get('pollingInterval', 'Not set')}")
544+
print()
545+
546+
except Exception as e:
547+
print(f" ❌ Error in comprehensive update: {e}\n")
548+
549+
# Example 5: Update specific fields only
550+
print("6. Updating specific fields only:")
551+
try:
552+
# Only update the description, leave everything else unchanged
553+
specific_update = j1.update_question(
554+
question_id=question_id,
555+
description="This question focuses on specific security controls and compliance requirements."
556+
)
557+
558+
print(f" ✅ Successfully updated description only!")
559+
print(f" Description updated: {specific_update['description'][:50]}...")
560+
print(f" Title remains: {specific_update['title']}")
561+
print(f" Tags remain: {', '.join(specific_update['tags'])}")
562+
print()
563+
564+
except Exception as e:
565+
print(f" ❌ Error updating specific fields: {e}\n")
566+
567+
else:
568+
print(" ⚠️ No questions found in the account to update")
569+
print()
570+
571+
except Exception as e:
572+
print(f" ❌ Error finding questions to update: {e}\n")
573+
574+
# Example 7: Update with compliance metadata
575+
print("7. Updating question with compliance metadata:")
576+
try:
577+
if questions:
578+
compliance_update = j1.update_question(
579+
question_id=question_id,
580+
compliance={
581+
"standard": "CIS Controls",
582+
"requirements": ["6.1", "6.2"],
583+
"controls": ["Data Protection", "Access Control"]
584+
}
585+
)
586+
587+
print(f" ✅ Successfully updated compliance metadata!")
588+
if 'compliance' in compliance_update:
589+
compliance_data = compliance_update['compliance']
590+
if isinstance(compliance_data, dict):
591+
print(f" Standard: {compliance_data.get('standard', 'Not specified')}")
592+
print(f" Requirements: {', '.join(map(str, compliance_data.get('requirements', [])))}")
593+
print(f" Controls: {', '.join(map(str, compliance_data.get('controls', [])))}")
594+
else:
595+
print(f" Compliance data type: {type(compliance_data)}")
596+
print()
597+
598+
except Exception as e:
599+
print(f" ❌ Error updating compliance metadata: {e}\n")
600+
601+
# Example 8: Update with variables
602+
print("8. Updating question with variables:")
603+
try:
604+
if questions:
605+
variables_update = j1.update_question(
606+
question_id=question_id,
607+
variables=[
608+
{
609+
"name": "environment",
610+
"required": True,
611+
"default": "production"
612+
},
613+
{
614+
"name": "severity",
615+
"required": False,
616+
"default": "high"
617+
}
618+
]
619+
)
620+
621+
print(f" ✅ Successfully updated variables!")
622+
if 'variables' in variables_update:
623+
print(f" Number of variables: {len(variables_update['variables'])}")
624+
for var in variables_update['variables']:
625+
print(f" - {var['name']} (required: {var.get('required', False)}, default: {var.get('default', 'None')})")
626+
print()
627+
628+
except Exception as e:
629+
print(f" ❌ Error updating variables: {e}\n")
630+
440631
def question_use_cases(j1):
441632
"""Demonstrate real-world use cases for questions."""
442633

@@ -492,6 +683,42 @@ def question_use_cases(j1):
492683
pollingInterval="ONE_WEEK"
493684
)
494685
""")
686+
687+
# Use Case 4: Question Maintenance and Updates
688+
print("\nUse Case 4: Question Maintenance and Updates")
689+
print("-" * 50)
690+
print("Maintain and update existing questions:")
691+
print("""
692+
# Update question title and description
693+
updated_question = j1.update_question(
694+
question_id="existing-question-id",
695+
title="Updated Security Question Title",
696+
description="Updated description with new security requirements"
697+
)
698+
699+
# Update queries for better performance
700+
updated_question = j1.update_question(
701+
question_id="existing-question-id",
702+
queries=[
703+
{
704+
"name": "ImprovedQuery",
705+
"query": "FIND * WITH tag.Security='critical' LIMIT 1000",
706+
"version": "v2",
707+
"resultsAre": "BAD"
708+
}
709+
]
710+
)
711+
712+
# Add compliance metadata
713+
updated_question = j1.update_question(
714+
question_id="existing-question-id",
715+
compliance={
716+
"standard": "ISO 27001",
717+
"requirements": ["A.9.1", "A.9.2"],
718+
"controls": ["Access Control"]
719+
}
720+
)
721+
""")
495722

496723
def main():
497724
"""Run all question management examples."""
@@ -518,6 +745,10 @@ def main():
518745
time.sleep(1)
519746

520747
get_question_details_example(j1)
748+
time.sleep(1)
749+
750+
update_question_examples(j1)
751+
time.sleep(1)
521752

522753
question_use_cases(j1)
523754

jupiterone/client.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
UPSERT_PARAMETER,
5959
UPDATE_ENTITYV2,
6060
INVOKE_INTEGRATION_INSTANCE,
61+
UPDATE_QUESTION,
6162
)
6263

6364
class JupiterOneClient:
@@ -1447,6 +1448,100 @@ def create_question(
14471448

14481449
return response["data"]["createQuestion"]
14491450

1451+
def update_question(
1452+
self,
1453+
question_id: str,
1454+
title: str = None,
1455+
description: str = None,
1456+
queries: List[Dict] = None,
1457+
tags: List[str] = None,
1458+
**kwargs
1459+
) -> Dict:
1460+
"""
1461+
Update an existing question in the J1 account.
1462+
1463+
Args:
1464+
question_id (str): The unique ID of the question to update (required)
1465+
title (str, optional): New title for the question
1466+
description (str, optional): New description for the question
1467+
queries (List[Dict], optional): Updated list of queries
1468+
tags (List[str], optional): Updated list of tags
1469+
**kwargs: Additional optional parameters:
1470+
- compliance (Dict): Compliance metadata
1471+
- variables (List[Dict]): Variable definitions for the queries
1472+
- showTrend (bool): Whether to show trend data
1473+
- pollingInterval (str): How often to run the queries
1474+
1475+
Returns:
1476+
Dict: The updated question object
1477+
1478+
Raises:
1479+
ValueError: If question_id is not provided
1480+
JupiterOneApiError: If the question update fails or other API errors occur
1481+
1482+
Example:
1483+
# Update question title and description
1484+
updated_question = j1_client.update_question(
1485+
question_id="fcc0507d-0473-43a2-b083-9d5571b92ae7",
1486+
title="Environment-Specific Resource Audit - UPDATED",
1487+
description="Audit resources by environment and cost center tags"
1488+
)
1489+
1490+
# Update queries and tags
1491+
updated_question = j1_client.update_question(
1492+
question_id="fcc0507d-0473-43a2-b083-9d5571b92ae7",
1493+
queries=[{
1494+
"name": "EnvironmentResources",
1495+
"query": "FIND * WITH tag.Production = true",
1496+
"version": None,
1497+
"resultsAre": "INFORMATIVE"
1498+
}],
1499+
tags=["audit", "tagging", "cost-management"]
1500+
)
1501+
1502+
# Comprehensive update
1503+
updated_question = j1_client.update_question(
1504+
question_id="fcc0507d-0473-43a2-b083-9d5571b92ae7",
1505+
title="Environment-Specific Resource Audit - UPDATED",
1506+
description="Audit resources by environment and cost center tags",
1507+
queries=[{
1508+
"name": "EnvironmentResources",
1509+
"query": "FIND * WITH tag.Production = true",
1510+
"version": None,
1511+
"resultsAre": "INFORMATIVE"
1512+
}],
1513+
tags=["audit", "tagging", "cost-management"]
1514+
)
1515+
"""
1516+
if not question_id:
1517+
raise ValueError("question_id is required")
1518+
1519+
# Build the update object with only provided fields
1520+
update_data = {}
1521+
1522+
if title is not None:
1523+
update_data["title"] = title
1524+
if description is not None:
1525+
update_data["description"] = description
1526+
if queries is not None:
1527+
update_data["queries"] = queries
1528+
if tags is not None:
1529+
update_data["tags"] = tags
1530+
1531+
# Add any additional fields from kwargs
1532+
for key, value in kwargs.items():
1533+
if value is not None:
1534+
update_data[key] = value
1535+
1536+
# Execute the GraphQL mutation
1537+
variables = {
1538+
"id": question_id,
1539+
"update": update_data
1540+
}
1541+
1542+
response = self._execute_query(UPDATE_QUESTION, variables)
1543+
return response["data"]["updateQuestion"]
1544+
14501545
def get_compliance_framework_item_details(self, item_id: str = None):
14511546
"""Fetch Details of a Compliance Framework Requirement configured in J1 account"""
14521547
variables = {"input": {"id": item_id}}

0 commit comments

Comments
 (0)