Skip to content

Commit f0ce8cc

Browse files
authored
Merge pull request #1007 from microsoft/feature/cosmoseditor
Add admin Cosmos DB JSON document query and editor
2 parents 1906210 + 700e0b8 commit f0ce8cc

11 files changed

Lines changed: 1668 additions & 8 deletions

File tree

application/single_app/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
EXECUTOR_TYPE = 'thread'
9797
EXECUTOR_MAX_WORKERS = 30
9898
SESSION_TYPE = 'filesystem'
99-
VERSION = "0.250.047"
99+
VERSION = "0.250.051"
100100

101101
SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
102102
SESSION_COOKIE_HTTPONLY = os.getenv('SESSION_COOKIE_HTTPONLY', 'true').lower() != 'false'

application/single_app/functions_data_management.py

Lines changed: 474 additions & 0 deletions
Large diffs are not rendered by default.

application/single_app/route_backend_data_management.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,22 @@
1313
DATA_MANAGEMENT_OPERATION_DRY_RUN,
1414
DATA_MANAGEMENT_OPERATION_MIGRATION,
1515
DATA_MANAGEMENT_OPERATION_RESTORE,
16+
DataManagementCosmosEditorError,
1617
DataManagementSettingsValidationError,
1718
generate_data_management_encryption_key,
19+
get_data_management_cosmos_editor_containers,
20+
get_data_management_cosmos_editor_document,
1821
get_data_management_backup_summary,
1922
get_data_management_job_detail,
2023
get_data_management_jobs,
2124
get_data_management_migration_catalog,
2225
get_data_management_settings,
26+
log_data_management_cosmos_editor_activity,
2327
queue_data_management_job,
28+
query_data_management_cosmos_editor_documents,
2429
sanitize_data_management_job_for_admin,
2530
sanitize_data_management_settings_for_admin,
31+
save_data_management_cosmos_editor_document,
2632
summarize_data_management_migration_plan,
2733
submit_data_management_job,
2834
test_backup_storage_connection,
@@ -58,6 +64,32 @@ def _log_data_management_admin_action(action, description, additional_context=No
5864
)
5965

6066

67+
def _get_cosmos_editor_payload():
68+
payload = request.get_json(silent=True) or {}
69+
return payload if isinstance(payload, dict) else {}
70+
71+
72+
def _cosmos_editor_error_status(exc, default_status=400):
73+
status_code = getattr(exc, "status_code", None)
74+
if status_code == 404:
75+
return 404
76+
if status_code == 412:
77+
return 409
78+
return default_status
79+
80+
81+
def _log_cosmos_editor_failure(action, message, details=None):
82+
admin_user_id, admin_email = _get_admin_context()
83+
log_data_management_cosmos_editor_activity(
84+
admin_user_id,
85+
admin_email,
86+
action,
87+
"failed",
88+
message,
89+
details=details or {},
90+
)
91+
92+
6193
def register_route_backend_data_management(bp):
6294
@bp.route("/api/admin/data-management/settings", methods=["GET"])
6395
@swagger_route(security=get_auth_security())
@@ -201,6 +233,160 @@ def test_admin_data_management_target_enhanced_citation_storage():
201233
return jsonify({"success": False, "error": "Target Enhanced Citation Storage connection test failed."}), 400
202234
return jsonify(result), 200
203235

236+
@bp.route("/api/admin/data-management/cosmos-editor/containers", methods=["GET"])
237+
@swagger_route(security=get_auth_security())
238+
@login_required
239+
@admin_required
240+
def list_admin_data_management_cosmos_editor_containers():
241+
return jsonify({
242+
"success": True,
243+
"containers": get_data_management_cosmos_editor_containers(),
244+
}), 200
245+
246+
@bp.route("/api/admin/data-management/cosmos-editor/danger-acknowledgement", methods=["POST"])
247+
@swagger_route(security=get_auth_security())
248+
@login_required
249+
@admin_required
250+
def acknowledge_admin_data_management_cosmos_editor_danger():
251+
admin_user_id, admin_email = _get_admin_context()
252+
log_data_management_cosmos_editor_activity(
253+
admin_user_id,
254+
admin_email,
255+
"cosmos_editor_danger_acknowledged",
256+
"success",
257+
"Acknowledged the Cosmos DB editor danger prompt.",
258+
{"prompt": "interface_entry"},
259+
)
260+
return jsonify({"success": True}), 200
261+
262+
@bp.route("/api/admin/data-management/cosmos-editor/query", methods=["POST"])
263+
@swagger_route(security=get_auth_security())
264+
@login_required
265+
@admin_required
266+
def query_admin_data_management_cosmos_editor_documents():
267+
payload = _get_cosmos_editor_payload()
268+
admin_user_id, admin_email = _get_admin_context()
269+
try:
270+
result = query_data_management_cosmos_editor_documents(
271+
payload.get("container"),
272+
query_text=payload.get("query"),
273+
page_size=payload.get("page_size"),
274+
continuation_token=payload.get("continuation_token"),
275+
admin_user_id=admin_user_id,
276+
admin_email=admin_email,
277+
)
278+
except DataManagementCosmosEditorError as exc:
279+
_log_cosmos_editor_failure(
280+
"cosmos_editor_query_rejected",
281+
"Rejected a Cosmos DB editor query.",
282+
{"container": payload.get("container"), "error": str(exc)},
283+
)
284+
return jsonify({"success": False, "error": "Cosmos DB editor query was rejected."}), 400
285+
except Exception as exc:
286+
log_event(
287+
"[DataManagement] Cosmos editor query failed.",
288+
{"container": payload.get("container"), "error": str(exc)},
289+
level=logging.WARNING,
290+
)
291+
_log_cosmos_editor_failure(
292+
"cosmos_editor_query_failed",
293+
"Cosmos DB editor query failed.",
294+
{"container": payload.get("container"), "status_code": getattr(exc, "status_code", None), "error": str(exc)},
295+
)
296+
return jsonify({"success": False, "error": "Cosmos DB editor query failed."}), _cosmos_editor_error_status(exc)
297+
return jsonify({"success": True, **result}), 200
298+
299+
@bp.route("/api/admin/data-management/cosmos-editor/document", methods=["POST"])
300+
@swagger_route(security=get_auth_security())
301+
@login_required
302+
@admin_required
303+
def get_admin_data_management_cosmos_editor_document():
304+
payload = _get_cosmos_editor_payload()
305+
admin_user_id, admin_email = _get_admin_context()
306+
try:
307+
result = get_data_management_cosmos_editor_document(
308+
payload.get("container"),
309+
payload.get("id"),
310+
payload.get("partition_key"),
311+
admin_user_id=admin_user_id,
312+
admin_email=admin_email,
313+
)
314+
except DataManagementCosmosEditorError as exc:
315+
log_event(
316+
"[DataManagement] Cosmos editor document open rejected.",
317+
{"container": payload.get("container"), "document_id": payload.get("id"), "error": str(exc)},
318+
level=logging.WARNING,
319+
)
320+
_log_cosmos_editor_failure(
321+
"cosmos_editor_document_rejected",
322+
"Rejected a Cosmos DB editor document open request.",
323+
{"container": payload.get("container"), "document_id": payload.get("id"), "error": str(exc)},
324+
)
325+
return jsonify({"success": False, "error": "Cosmos DB document request was invalid."}), 400
326+
except Exception as exc:
327+
status_code = _cosmos_editor_error_status(exc, default_status=400)
328+
log_event(
329+
"[DataManagement] Cosmos editor document open failed.",
330+
{"container": payload.get("container"), "document_id": payload.get("id"), "error": str(exc)},
331+
level=logging.WARNING,
332+
)
333+
_log_cosmos_editor_failure(
334+
"cosmos_editor_document_failed",
335+
"Cosmos DB editor document open failed.",
336+
{"container": payload.get("container"), "document_id": payload.get("id"), "status_code": getattr(exc, "status_code", None), "error": str(exc)},
337+
)
338+
error_message = "Cosmos DB document was not found." if status_code == 404 else "Cosmos DB document could not be opened."
339+
return jsonify({"success": False, "error": error_message}), status_code
340+
return jsonify({"success": True, **result}), 200
341+
342+
@bp.route("/api/admin/data-management/cosmos-editor/document", methods=["PUT"])
343+
@swagger_route(security=get_auth_security())
344+
@login_required
345+
@admin_required
346+
def save_admin_data_management_cosmos_editor_document():
347+
payload = _get_cosmos_editor_payload()
348+
admin_user_id, admin_email = _get_admin_context()
349+
try:
350+
result = save_data_management_cosmos_editor_document(
351+
payload.get("container"),
352+
payload.get("id"),
353+
payload.get("partition_key"),
354+
payload.get("etag"),
355+
payload.get("document"),
356+
confirmation_accepted=payload.get("confirmation_accepted") is True,
357+
confirmation_phrase=payload.get("confirmation_phrase"),
358+
admin_user_id=admin_user_id,
359+
admin_email=admin_email,
360+
)
361+
except DataManagementCosmosEditorError as exc:
362+
_log_cosmos_editor_failure(
363+
"cosmos_editor_save_rejected",
364+
"Rejected a Cosmos DB editor save request.",
365+
{"container": payload.get("container"), "document_id": payload.get("id"), "error": str(exc)},
366+
)
367+
return jsonify({"success": False, "error": "Cosmos DB document save request was rejected."}), 400
368+
except Exception as exc:
369+
status_code = _cosmos_editor_error_status(exc, default_status=400)
370+
log_event(
371+
"[DataManagement] Cosmos editor save failed.",
372+
{"container": payload.get("container"), "document_id": payload.get("id"), "error": str(exc)},
373+
level=logging.WARNING,
374+
exceptionTraceback=True,
375+
)
376+
_log_cosmos_editor_failure(
377+
"cosmos_editor_save_failed",
378+
"Cosmos DB editor save failed.",
379+
{"container": payload.get("container"), "document_id": payload.get("id"), "status_code": getattr(exc, "status_code", None), "error": str(exc)},
380+
)
381+
if status_code == 409:
382+
error_message = "Cosmos DB document changed after it was opened. Refresh before saving again."
383+
elif status_code == 404:
384+
error_message = "Cosmos DB document was not found."
385+
else:
386+
error_message = "Cosmos DB document could not be saved."
387+
return jsonify({"success": False, "error": error_message}), status_code
388+
return jsonify({"success": True, **result}), 200
389+
204390
@bp.route("/api/admin/data-management/jobs", methods=["GET"])
205391
@swagger_route(security=get_auth_security())
206392
@login_required

application/single_app/static/css/styles.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,3 +1240,19 @@ main {
12401240
border-left-color: #495057;
12411241
color: #adb5bd;
12421242
}
1243+
1244+
/* Cosmos editor modal */
1245+
#data-management-cosmos-editor-results-modal .cosmos-editor-results-modal-body {
1246+
overflow-x: hidden;
1247+
}
1248+
1249+
#data-management-cosmos-editor-results-modal .cosmos-editor-results-list {
1250+
max-height: calc(100vh - 18rem);
1251+
overflow-y: auto;
1252+
}
1253+
1254+
@media (max-width: 991.98px) {
1255+
#data-management-cosmos-editor-results-modal .cosmos-editor-results-list {
1256+
max-height: 18rem;
1257+
}
1258+
}

0 commit comments

Comments
 (0)