DynamicDashboardDataComponent.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. using InABox.Clients;
  2. using InABox.Core;
  3. using InABox.Scripting;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Diagnostics.CodeAnalysis;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. namespace InABox.Wpf.Dashboard;
  11. public interface IDynamicDashboardTable
  12. {
  13. string Key { get; set; }
  14. IEnumerable<CoreColumn> GetColumns(DynamicDashboardDataComponent data);
  15. }
  16. public interface IDynamicDashboardDataQuery : IDynamicDashboardTable
  17. {
  18. Type Type { get; }
  19. IFilter? Filter { get; set; }
  20. IColumns Columns { get; set; }
  21. ISortOrder? SortOrder { get; set; }
  22. IEnumerable<CoreColumn> IDynamicDashboardTable.GetColumns(DynamicDashboardDataComponent data) =>
  23. Columns.Columns().Select(x => new CoreColumn
  24. {
  25. ColumnName = x.Name,
  26. DataType = x.Type
  27. });
  28. }
  29. public class DynamicDashboardDataQuery<T> : IDynamicDashboardDataQuery
  30. where T : Entity, IRemotable, new()
  31. {
  32. public string Key { get; set; } = typeof(T).Name;
  33. public Filter<T>? Filter { get; set; }
  34. public Columns<T> Columns { get; set; } = Core.Columns.None<T>();
  35. public SortOrder<T>? SortOrder { get; set; } = null;
  36. #region IDynamicDashboardDataQuery
  37. Type IDynamicDashboardDataQuery.Type => typeof(T);
  38. IFilter? IDynamicDashboardDataQuery.Filter
  39. {
  40. get => Filter;
  41. set => Filter = value as Filter<T>;
  42. }
  43. IColumns IDynamicDashboardDataQuery.Columns
  44. {
  45. get => Columns;
  46. set => Columns = (value as Columns<T>) ?? Core.Columns.None<T>();
  47. }
  48. ISortOrder? IDynamicDashboardDataQuery.SortOrder
  49. {
  50. get => SortOrder;
  51. set => SortOrder = value as SortOrder<T>;
  52. }
  53. #endregion
  54. }
  55. public class DynamicDashboardAdditionalTable : IDynamicDashboardTable
  56. {
  57. public string Key { get; set; } = "";
  58. private string? _script;
  59. public string? Script
  60. {
  61. get => _script;
  62. set
  63. {
  64. if(_script != value)
  65. {
  66. _script = value;
  67. _scriptDocument = null;
  68. }
  69. }
  70. }
  71. public IEnumerable<CoreColumn> GetColumns(DynamicDashboardDataComponent data) => SetupColumns(data);
  72. private ScriptDocument? _scriptDocument;
  73. private ScriptDocument? ScriptDocument
  74. {
  75. get
  76. {
  77. if(_scriptDocument is null && Script is not null)
  78. {
  79. _scriptDocument = new(Script);
  80. _scriptDocument.Compile();
  81. }
  82. return _scriptDocument;
  83. }
  84. }
  85. public static string DefaultScript()
  86. {
  87. return @"using InABox.Wpf.Dashboard;
  88. public class Module
  89. {
  90. public IEnumerable<CoreColumn> SetupColumns(DynamicDashboardDataComponent data)
  91. {
  92. // Return the list of columns for this table.
  93. return [
  94. new CoreColumn(typeof(string), ""Column1""),
  95. new CoreColumn(typeof(int), ""Column2"")];
  96. }
  97. public void PopulateTable(CoreTable table, DynamicDashboardData data)
  98. {
  99. // Populate 'table' using the data from 'data'.
  100. // For example, to add rows to the table:
  101. var newRow = table.NewRow();
  102. newRow[""Column1""] = ""First Column Data"";
  103. newRow[""Column2""] = 1;
  104. table.Rows.Add(newRow);
  105. }
  106. }";
  107. }
  108. private IEnumerable<CoreColumn> SetupColumns(DynamicDashboardDataComponent data)
  109. {
  110. if (ScriptDocument is null) return [];
  111. var method = ScriptDocument.GetMethod(methodName: "SetupColumns");
  112. if(method is not null)
  113. {
  114. return method.Invoke(ScriptDocument!.GetObject(), [data]) as IEnumerable<CoreColumn>
  115. ?? [];
  116. }
  117. else
  118. {
  119. return [];
  120. }
  121. }
  122. public void PopulateTable(CoreTable table, DynamicDashboardData data)
  123. {
  124. if (ScriptDocument is null) return;
  125. ScriptDocument.Execute(methodname: "PopulateTable", parameters: [table, data]);
  126. }
  127. }
  128. public class DynamicDashboardDataComponent
  129. {
  130. public List<IDynamicDashboardDataQuery> Queries { get; set; } = new();
  131. public List<DynamicDashboardAdditionalTable> AdditionalTables { get; set; } = new();
  132. public IEnumerable<IDynamicDashboardTable> Tables => Queries.Concat<IDynamicDashboardTable>(AdditionalTables);
  133. public IDynamicDashboardTable GetTable(string key)
  134. {
  135. return Tables.FirstOrDefault(x => x.Key == key)
  136. ?? throw new KeyNotFoundException($"Data table '{key}' does not exist.");
  137. }
  138. public bool TryGetTable(string key, [NotNullWhen(true)] out IDynamicDashboardTable? query)
  139. {
  140. query = Tables.FirstOrDefault(x => x.Key == key);
  141. return query != null;
  142. }
  143. private void PopulateAdditionalTables(DynamicDashboardData data)
  144. {
  145. foreach(var tableDef in AdditionalTables)
  146. {
  147. var table = new CoreTable();
  148. table.Columns.AddRange(tableDef.GetColumns(this));
  149. tableDef.PopulateTable(table, data);
  150. data.Data.Add(tableDef.Key, table);
  151. }
  152. }
  153. /// <summary>
  154. /// Run the query to get the data according to the definitions of <see cref="Queries"/>.
  155. /// </summary>
  156. /// <param name="maxRecords">Set to a non-<see langword="null"/> value to limit the number of records returned. Used for preview mode.</param>
  157. public DynamicDashboardData RunQuery(int? maxRecords = null)
  158. {
  159. var range = maxRecords.HasValue ? CoreRange.Database(maxRecords.Value) : null;
  160. var queryDefs = Queries.Select(x => new KeyedQueryDef(x.Key, x.Type, x.Filter, x.Columns, x.SortOrder, range));
  161. var results = Client.QueryMultiple(queryDefs);
  162. var data = new DynamicDashboardData(results.Results);
  163. PopulateAdditionalTables(data);
  164. return data;
  165. }
  166. /// <summary>
  167. /// Run the query asynchronously to get the data according to the definitions of <see cref="Queries"/>.
  168. /// </summary>
  169. /// <param name="maxRecords">Set to a non-<see langword="null"/> value to limit the number of records returned. Used for preview mode.</param>
  170. public async Task<DynamicDashboardData> RunQueryAsync(int? maxRecords = null)
  171. {
  172. var range = maxRecords.HasValue ? CoreRange.Database(maxRecords.Value) : null;
  173. var queryDefs = Queries.Select(x => new KeyedQueryDef(x.Key, x.Type, x.Filter, x.Columns, x.SortOrder, range));
  174. var results = await Client.QueryMultipleAsync(queryDefs);
  175. var data = new DynamicDashboardData(results.Results);
  176. PopulateAdditionalTables(data);
  177. return data;
  178. }
  179. }
  180. public class DynamicDashboardData(Dictionary<string, CoreTable> data)
  181. {
  182. public Dictionary<string, CoreTable> Data { get; set; } = data;
  183. public bool TryGetData(string key, [NotNullWhen(true)] out CoreTable? query)
  184. {
  185. return Data.TryGetValue(key, out query);
  186. }
  187. }