@page "/user/find" @page "/user/find/{DatabaseStr}/{CollectionStr}" @page "/user/find/{DatabaseStr}/{DatabaseInstanceStr}/{CollectionStr}" @attribute [Authorize] @inject NavigationManager NavigationManager @inject IUserSession UserSession @inject IJSRuntime JsRuntime @inject IAuthorizationService Auth @* * dbMango * * Copyright 2025 Deutsche Bank AG * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *@

Find: @SelectedCollection

Filter:
Projection: (use {} to see everything, _id MUST be present in the result)
@if (!IsReady) { } else if (ShowAsJson) { } else { }
@code { [CascadingParameter] public IModalService Modal { get; set; } = null!; [Parameter] public string? DatabaseStr { get; set; } [Parameter] public string? DatabaseInstanceStr { get; set; } [Parameter] public string? CollectionStr { get; set; } private string Text { get; set; } = @"{ ""_id"": { ""$ne"" : """" } }"; private string Project { get; set; } = @"{ }"; private IReadOnlyCollection Collections { get; set; } = []; private string SelectedCollection { get => UserSession.Collection; set { if (UserSession.Collection == value) return; UserSession.Collection = value; SyncUrl(); InvokeAsync(StateHasChanged); } } private string Database { get => UserSession.Database; set { if (UserSession.Database == value) return; UserSession.Database = value; SyncUrl(); InvokeAsync(StateHasChanged); } } private string DatabaseInstance { get => UserSession.DatabaseInstance; set { if (UserSession.DatabaseInstance == value) return; UserSession.DatabaseInstance = value; SyncUrl(); } } private string Timeout { get; set; } = "20"; private string FetchSize { get; set; } = "100"; private bool IsReady { get; set; } private bool ShowAsJson { get; set; } private string Result { get; set; } = "{}"; private List ResultBson { get; set; } = []; public ArrayBasedPivotData ResultPivot { get; set; } = new([]); private EditContext? _editContext; private EditContext EditContext => _editContext!; protected override void OnInitialized() { _editContext = new (this); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) return; if (string.IsNullOrWhiteSpace(DatabaseStr)) DatabaseStr = Database; else Database = DatabaseStr; if (string.IsNullOrWhiteSpace(DatabaseInstanceStr)) DatabaseInstanceStr = DatabaseInstance; else DatabaseInstance = DatabaseInstanceStr; if (string.IsNullOrWhiteSpace(CollectionStr)) CollectionStr = SelectedCollection; else SelectedCollection = CollectionStr; SyncUrl(); var text = await JsRuntime.LoadFromLocalStorage("Find"); if (!string.IsNullOrWhiteSpace(text)) Text = text; _ = Task.Run(async () => { try { var admin = UserSession.MongoDbAdmin; Collections = await admin.ListCollections(); if (!Collections.Contains(SelectedCollection)) SelectedCollection = Collections.FirstOrDefault() ?? ""; IsReady = true; await InvokeAsync(StateHasChanged); } catch (Exception e) { await Display(e); } }); } enum ActionType { Find, Count, Explain } private async Task Run(ActionType actionType) { var cts = new CancellationTokenSource(TimeSpan.FromSeconds(int.Parse(Timeout))); try { IsReady = false; await InvokeAsync(StateHasChanged); await JsRuntime.SaveToLocalStorage("Find", Text, cts.Token); if ( int.TryParse(FetchSize, out var limit) == false ) limit = 0; var res = actionType switch { ActionType.Find => await RunFind(Text, Project, limit, cts.Token), ActionType.Count => await RunCount (Text, cts.Token), ActionType.Explain => await RunExplain(Text, cts.Token), _ => throw new ArgumentOutOfRangeException(nameof(actionType), actionType, null) }; ResultPivot = res.Item1; ResultBson = res.Item2; await Display(ResultBson); } catch (Exception e) { await Display(e); } finally { IsReady = true; await InvokeAsync(StateHasChanged); } } private Task Run() => Run(ActionType.Find); private Task Count() => Run(ActionType.Count); private Task Explain() => Run(ActionType.Explain); private Task Display(Exception e) { ShowAsJson = true; Result = e.ToString(); return InvokeAsync(StateHasChanged); } private Task Display(IEnumerable res) { Result = res.ToJson(new() { Indent = true }); return InvokeAsync(StateHasChanged); } private async Task<(ArrayBasedPivotData, List)> RunExplain(string text, CancellationToken token) { var service = UserSession.MongoDb; var command = $@"{{ ""find"" : ""{SelectedCollection}"", ""filter"" : {text} }}"; var res = await service.ExplainAsync(command, token); ShowAsJson = true; return (ArrayBasedPivotData.NoData, [res]); } private async Task<(ArrayBasedPivotData, List)> RunFind(string text, string project , int limit, CancellationToken token) { var service = UserSession.MongoDb; var results = service.FindAsync(text, false, project, limit: limit <= 0 ? null : limit, token: token); var fieldMap = new ConcurrentDictionary { ["find"] = new(false) }; var (pd, list) = await MongoDbDataSource.FetchPivotData( "find", "find", fieldMap, results, null, true, int.Parse(FetchSize), true, token); if (list.Count == 0) list.Add(BsonDocument.Parse(@$"{{""Result"": ""No data fetched from {SelectedCollection}""}}")); return (pd, list); } private async Task<(ArrayBasedPivotData, List)> RunCount(string text, CancellationToken token) { var service = UserSession.MongoDb; var result = await service.CountAsync(text, token); var pd = new ArrayBasedPivotData(["Count"]); pd.Add([result]); return (pd, [BsonDocument.Parse($"{{ Count: {result} }}"), ]); } private void SyncUrl() { var url = NavigationManager.BaseUri + $"user/find/{Database}"; if (!string.IsNullOrWhiteSpace(DatabaseInstance)) url += $"/{DatabaseInstance}"; url += $"/{SelectedCollection}"; JsRuntime.InvokeAsync("DashboardUtils.ChangeUrl", url); } private async Task Format() { Text = JsonUtils.FormatJson(Text); await InvokeAsync(StateHasChanged); } private async Task OnCellClick(DynamicObject row, string columnName) { try { var id = TableControl.GetDynamicMember(row, "_id"); if (id == null) return true; var bson = ResultBson.FirstOrDefault(x => x["_id"].ToString() == id.ToString()); if (bson == null) return true; var oldId = bson["_id"]; var enableWrite = await Auth.AuthorizeAsync( UserSession.User.GetUser(), UserSession.Database, [new WriteAccessRequirement()]); var res = await ShowBsonDialog(Modal, id.ToString()!, bson, enableWrite.Succeeded); if (res == null) return true; if (enableWrite.Succeeded) { if (res.Value.ShouldUpdate) { bson = BsonDocument.Parse(res.Value.Json); await OnUpdate(oldId, bson); } else await OnDelete(oldId); } } catch (Exception e) { await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", e); } return true; } private async Task OnDelete(BsonValue id) { var ticket = await Shell.CanExecuteCommand(UserSession, InvokeAsync, Modal); if (string.IsNullOrWhiteSpace(ticket)) return; var r = await ModalDialogUtils.ShowConfirmationDialog(Modal, $"Delete {id}", "Are you sure to delete document?"); if (r.Cancelled) return; var command = BsonDocument.Parse($@"{{ ""delete"": ""{SelectedCollection}"", ""deletes"": [{{ ""q"": {{ }}, ""limit"": 1 }}] }}"); var idDoc = new BsonDocument { ["_id"] = id }; command["deletes"][0]["q"] = idDoc; var cts = new CancellationTokenSource(TimeSpan.FromSeconds(int.Parse(Timeout))); Shell.UpdateComment(command, ticket, UserSession.User.GetEmail()); var res = await UserSession.MongoDbAdmin.RunCommand(command, cts.Token); var deleted = res["n"].ToInt32(); if (deleted != 1) { Result = res.ToJson(new() { Indent = true }) + "\n------------------------------------------------------------------\n"+ command.ToJson(new() { Indent = true }); ShowAsJson = true; await ModalDialogUtils.ShowInfoDialog(Modal, $"Delete {id}", "Failure"); await InvokeAsync(StateHasChanged); } else await ModalDialogUtils.ShowInfoDialog(Modal, $"Delete {id}", "Success"); } private async Task OnUpdate(BsonValue id, BsonDocument newBson) { var ticket = await Shell.CanExecuteCommand(UserSession, InvokeAsync, Modal); if (string.IsNullOrWhiteSpace(ticket)) return; var r = await ModalDialogUtils.ShowConfirmationDialog(Modal, $"Update {id}", "Are you sure to update document?"); if (r.Cancelled) return; var command = BsonDocument.Parse($@"{{ update: ""{SelectedCollection}"", updates: [ {{ q: {{ }}, u: {{ }}, upsert: false, multi: false, }} ], ordered: false, bypassDocumentValidation: false }}"); var idDoc = new BsonDocument { ["_id"] = id }; command["updates"][0]["q"] = idDoc; command["updates"][0]["u"] = newBson; Shell.UpdateComment(command, ticket, UserSession.User.GetEmail()); var cts = new CancellationTokenSource(TimeSpan.FromSeconds(int.Parse(Timeout))); var res = await UserSession.MongoDbAdmin.RunCommand(command, cts.Token); var modified = res["nModified"].ToInt32(); if (modified != 1) { Result = res.ToJson(new() { Indent = true }) + "\n------------------------------------------------------------------\n"+ command.ToJson(new() { Indent = true }); ShowAsJson = true; await ModalDialogUtils.ShowInfoDialog(Modal, $"Update {id}", "Failure"); await InvokeAsync(StateHasChanged); } else await ModalDialogUtils.ShowInfoDialog(Modal, $"Update {id}", "Success"); } public static async Task<(bool ShouldUpdate, string Json)?> ShowBsonDialog(IModalService service, string header, BsonDocument bson, bool enableWrite) { var parameters = new ModalParameters { { "Text", bson.ToJson(new() { Indent = true }) }, { "EnableWrite", enableWrite } }; var options = new ModalOptions { HideCloseButton = true, DisableBackgroundCancel = true }; var form = service.Show(header, parameters, options); var res = await form.Result; if (res.Cancelled) return null; var v = (MessageBoxJsonComponent.ResultType?)res.Data; if (v == null) return null; return (v.ShouldUpdate, v.Json); } }