dbMango/Rms.Risk.Mango/Pages/User/SavedQueries.razor
Alexander Shabarshov 2a7a24c9e7 Initial contribution
2025-11-03 14:43:26 +00:00

351 lines
13 KiB
Plaintext

@page "/user/saved-queries"
@page "/user/saved-queries/{DatabaseStr}"
@page "/user/saved-queries/{DatabaseStr}/{DatabaseInstanceStr}"
@attribute [Authorize]
@inject NavigationManager NavigationManager
@inject IUserSession UserSession
@inject IJSRuntime JsRuntime
@*
* 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.
*@
<h3>Saved queries</h3>
<style>
.tree-container {
height: calc(100vh - 80px);
width: 100%;
overflow: auto;
}
.tab-container {
width: 100%;
height: calc(100vh - 80px);
overflow: auto;
}
.tab-page {
padding-left: 10px;
padding-right: 10px;
}
.pivot-settings-tab {
width: 100%;
height: calc(100vh - 182px);
overflow: auto;
}
ul {
padding-left: 10px !important;
}
</style>
<AuthorizedOnly Policy="ReadAccess" Resource="@UserSession.Database">
<SplitPanel Orientation="horizontal" InitialSplit="0.3">
<First>
<TreeComponent TItem="GroupedPivot" RootNodes="@_rootNodes" OnNodeChanged="@HandleNodeChanged" @bind-SelectedNode="SelectedPivotNode"/>
</First>
<Second>
<div class="tab-container">
<TabControl PersistAllTabs="true">
<TabPage Text="Pivot">
<div class="pivot-navigator">
<PivotNavButtonsControl
Navigation="@_navigation"
IsRefreshEnabled="@IsRefreshEnabled"
IsExportEnabled="@IsExportEnabled"
RefreshPivotTriggered="@OnRefreshPivot"
CopyCsvTriggered="@OnCopyCsv"
ExportCsvTriggered="@OnExportCsv"
@bind-UseCache="@UseCache"
/>
<div class="fit-content">
<PivotTableComponent @ref="PivotTable"
Collections="@_collections"
@bind-PivotData="PivotData"
@bind-CurrentPivot="CurrentPivot"
SelectedCollectionNode="@SelectedCollectionNode"
SelectedPivotNode="@SelectedPivotNode?.Data"
Rows="@Rows"
@bind-UseCache="UseCache"
ExtraFilter="@GetExtraFilter()"
PivotService="@UserSession.PivotDataSource"
Navigation="@_navigation"
@bind-IsExportEnabled="IsExportEnabled"
@bind-LastRefresh="LastRefresh"
@bind-LastRefreshElapsed="LastRefreshElapsed" />
</div>
</div>
</TabPage>
<TabPage Text="Settings">
<PivotSettingsControl Vertical="false"
Pivot="SelectedPivotNode?.Data?.Pivot"
SelectedCollectionNode="@SelectedCollectionNode"
PivotService="@UserSession.PivotDataSource"
/>
</TabPage>
</TabControl>
</div>
</Second>
</SplitPanel>
</AuthorizedOnly>
@code {
[CascadingParameter] public IModalService Modal { get; set; } = null!;
[Parameter] public string? DatabaseStr { get; set; }
[Parameter] public string? DatabaseInstanceStr { get; set; }
private readonly List<TreeNode<GroupedPivot>> _rootNodes = [];
private Navigation<NavigationUnit> _navigation = null!;
private Dictionary<string, GroupedCollection> _groupedCollections = new();
private bool IsRefreshEnabled => SelectedPivotNode != null && SelectedPivotNode.Data != null;
private bool IsExportEnabled { get; set; } = true;
private DateTime LastRefresh { get; set; }
private TimeSpan LastRefreshElapsed { get; set; }
private PivotTableComponent PivotTable { get; set; } = null!;
private bool UseCache { get; set; } = true;
private IPivotedData? PivotData { get; set; }
private PivotDefinition? CurrentPivot { get; set; }
private int Rows { get; set; } = 35;
private string Database
{
get => UserSession.Database;
set
{
if (UserSession.Database == value)
return;
UserSession.Database = value;
SyncUrl();
}
}
public TreeNode<GroupedPivot>? SelectedPivotNode
{
get;
set
{
if (field == value)
return;
field = value;
//if (field?.Data != null)
//{
// // If a pivot is selected, navigate to the pivot page
// var url = NavigationManager.BaseUri + $"user/pivot/{Database}/{field.Data.Collection}/{field.Data.Name}/{UserSession.Department}";
// JsRuntime.InvokeAsync<string>("DashboardUtils.ChangeUrl", url);
//}
//else
//{
// // If no pivot is selected, just update the state
// StateHasChanged();
//}
}
}
// Extracts the name of the direct descendant of the root parent (Base)
private TreeNode<GroupedPivot>? BaseNode
{
get
{
var node = SelectedPivotNode;
if (node == null) return null;
// Traverse up to the root
while (node.Parent != null && node.Parent.Parent != null)
{
node = node.Parent;
}
// node is now the direct child of root (i.e., Base)
return node.Parent != null ? node : null;
}
}
private string? Base => BaseNode?.Label.Split(":").FirstOrDefault();
private string? Collection => BaseNode?.Label.Split(":").LastOrDefault();
private List<GroupedCollection> _collections = [];
// Concatenation of names of nodes between Collection and SelectedPivotNode, excluding both
private string Path
{
get
{
// Now, walk from SelectedPivotNode up to Collection, excluding both
var names = new List<string>();
var temp = SelectedPivotNode;
var baseNode = BaseNode;
while (temp != null && temp != baseNode)
{
if (temp != SelectedPivotNode) // Exclude SelectedPivotNode itself
names.Insert(0, temp.Label);
temp = temp.Parent;
}
return string.Join("/", names);
}
}
private GroupedCollection? SelectedCollectionNode
{
get
{
if (SelectedPivotNode == null || BaseNode == null)
return null;
// Find the grouped collection that matches the current collection name
_groupedCollections.TryGetValue(BaseNode.Label, out var groupedCollection);
return groupedCollection;
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
return;
_navigation = new(PivotTable.Navigate);
if (string.IsNullOrWhiteSpace(DatabaseStr))
DatabaseStr = UserSession.Database;
else
UserSession.Database = DatabaseStr;
if (string.IsNullOrWhiteSpace(DatabaseInstanceStr))
DatabaseInstanceStr = UserSession.DatabaseInstance;
else
UserSession.DatabaseInstance = DatabaseInstanceStr;
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var ds = UserSession.PivotDataSource;
_collections = await ds.GetAllMeta(token: cts.Token);
if (_collections.Count == 0)
{
await ModalDialogUtils.ShowInfoDialog(Modal, "Problem", "No collections found that have corresponding -Meta collections.");
return;
}
_groupedCollections = _collections
.Where(x => !x.IsGroup)
.ToDictionary(x => x.CollectionNameWithPrefix, x => x);
var root = new TreeNode<GroupedPivot>()
{
Label = "Saved queries",
Data = null,
Parent = null,
IsExpanded = true
};
PopulateRootNodes(ds, root, _collections, cts.Token);
_rootNodes.Add(root);
SyncUrl();
StateHasChanged();
}
private static void PopulateRootNodes(IPivotTableDataSource ds, TreeNode<GroupedPivot> root, List<GroupedCollection> collections, CancellationToken token)
{
// Clear any existing children
root.Children.Clear();
foreach (var collection in collections.Where( x => !x.IsGroup))
{
// Each collection becomes a child node under the root
var node = new TreeNode<GroupedPivot>
{
Label = collection.CollectionNameWithoutPrefix,
Data = null,
Parent = root,
IsExpanded = false
};
root.Children.Add(node);
// Group pivots by GroupName (null or empty group names go under "Ungrouped")
var groupedPivots = collection.Pivots
.Where(x => !x.IsGroup)
.GroupBy(p => string.IsNullOrWhiteSpace(p.Pivot.Group) ? "Ungrouped" : p.Pivot.Group)
.OrderBy(g => g.Key);
foreach (var group in groupedPivots)
{
// Create a group node under the collection node
var groupNode = new TreeNode<GroupedPivot>
{
Label = group.Key,
Data = null,
Parent = root.Children.Last(), // The collection node just added
IsExpanded = false
};
root.Children.Last().Children.Add(groupNode);
// Add all pivots in this group as children of the group node
foreach (var pivot in group.OrderBy(x => x.Pivot.Name))
{
var childNode = new TreeNode<GroupedPivot>
{
Label = pivot.Pivot.Name,
Data = pivot,
Parent = groupNode,
IsExpanded = false
};
groupNode.Children.Add(childNode);
}
}
}
}
private async Task HandleNodeChanged(TreeNode<GroupedPivot> node)
{
if (node.Data == null)
{
// If the node has no data, it means it's a collection or group node, so we don't do anything
return;
}
node.Data.Pivot.Name = node.Label; // Update the pivot name to match the node label
await InvokeAsync(StateHasChanged); // Refresh the UI
}
private void SyncUrl()
{
var url = NavigationManager.BaseUri + $"user/saved-queries/{Database}";
if (!string.IsNullOrWhiteSpace(UserSession.DatabaseInstance))
url += $"/{UserSession.DatabaseInstance}";
JsRuntime.InvokeAsync<string>("DashboardUtils.ChangeUrl", url);
}
private readonly FilterExpressionTree.ExpressionGroup _noFilter = new ();
private FilterExpressionTree.ExpressionGroup GetExtraFilter() => _noFilter;
private Task OnRefreshPivot() => PivotTable.RunPivot();
private Task OnCopyCsv() => PivotTable.CopyCsv();
private Task OnExportCsv() => Task.CompletedTask; //PivotTable.ExportCsv(Uri.EscapeDataString($"{CobStr}-{Department}-{Pivot?.Pivot.Name}.csv"));
}