@memberjunction/ng-dashboards 5.22.0 → 5.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +51 -0
  2. package/dist/AI/components/agents/agent-configuration.component.d.ts.map +1 -1
  3. package/dist/AI/components/agents/agent-configuration.component.js +364 -362
  4. package/dist/AI/components/agents/agent-configuration.component.js.map +1 -1
  5. package/dist/AI/components/agents/agent-editor.component.js +2 -2
  6. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts +947 -64
  7. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts.map +1 -1
  8. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js +7645 -430
  9. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js.map +1 -1
  10. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +285 -6
  11. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
  12. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +2454 -277
  13. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
  14. package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -1
  15. package/dist/AI/components/execution-monitoring.component.js +191 -197
  16. package/dist/AI/components/execution-monitoring.component.js.map +1 -1
  17. package/dist/AI/components/models/model-management.component.js +9 -8
  18. package/dist/AI/components/models/model-management.component.js.map +1 -1
  19. package/dist/AI/components/prompts/prompt-management.component.js +305 -299
  20. package/dist/AI/components/prompts/prompt-management.component.js.map +1 -1
  21. package/dist/AI/components/system/system-configuration.component.js +319 -313
  22. package/dist/AI/components/system/system-configuration.component.js.map +1 -1
  23. package/dist/AI/components/vectors/vector-management-resource.component.d.ts +20 -2
  24. package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
  25. package/dist/AI/components/vectors/vector-management-resource.component.js +419 -232
  26. package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
  27. package/dist/APIKeys/api-applications-panel.component.js +10 -12
  28. package/dist/APIKeys/api-applications-panel.component.js.map +1 -1
  29. package/dist/APIKeys/api-key-create-dialog.component.js +13 -19
  30. package/dist/APIKeys/api-key-create-dialog.component.js.map +1 -1
  31. package/dist/APIKeys/api-key-edit-panel.component.js +12 -14
  32. package/dist/APIKeys/api-key-edit-panel.component.js.map +1 -1
  33. package/dist/APIKeys/api-scopes-panel.component.js +61 -68
  34. package/dist/APIKeys/api-scopes-panel.component.js.map +1 -1
  35. package/dist/APIKeys/api-usage-panel.component.js +10 -11
  36. package/dist/APIKeys/api-usage-panel.component.js.map +1 -1
  37. package/dist/Actions/components/actions-list-view.component.js +82 -96
  38. package/dist/Actions/components/actions-list-view.component.js.map +1 -1
  39. package/dist/Actions/components/actions-overview.component.js +130 -134
  40. package/dist/Actions/components/actions-overview.component.js.map +1 -1
  41. package/dist/Actions/components/categories-list-view.component.d.ts.map +1 -1
  42. package/dist/Actions/components/categories-list-view.component.js +40 -46
  43. package/dist/Actions/components/categories-list-view.component.js.map +1 -1
  44. package/dist/Actions/components/code-management.component.js +2 -2
  45. package/dist/Actions/components/code-management.component.js.map +1 -1
  46. package/dist/Actions/components/entity-integration.component.js +2 -2
  47. package/dist/Actions/components/entity-integration.component.js.map +1 -1
  48. package/dist/Actions/components/execution-monitoring.component.js +127 -132
  49. package/dist/Actions/components/execution-monitoring.component.js.map +1 -1
  50. package/dist/Actions/components/executions-list-view.component.js +2 -2
  51. package/dist/Actions/components/executions-list-view.component.js.map +1 -1
  52. package/dist/Actions/components/explorer/action-card.component.js +11 -17
  53. package/dist/Actions/components/explorer/action-card.component.js.map +1 -1
  54. package/dist/Actions/components/explorer/action-explorer.component.js +5 -11
  55. package/dist/Actions/components/explorer/action-explorer.component.js.map +1 -1
  56. package/dist/Actions/components/explorer/action-list-item.component.js +8 -10
  57. package/dist/Actions/components/explorer/action-list-item.component.js.map +1 -1
  58. package/dist/Actions/components/explorer/action-toolbar.component.js +112 -133
  59. package/dist/Actions/components/explorer/action-toolbar.component.js.map +1 -1
  60. package/dist/Actions/components/explorer/action-tree-panel.component.js +63 -83
  61. package/dist/Actions/components/explorer/action-tree-panel.component.js.map +1 -1
  62. package/dist/Actions/components/explorer/new-action-panel.component.js +17 -21
  63. package/dist/Actions/components/explorer/new-action-panel.component.js.map +1 -1
  64. package/dist/Actions/components/explorer/new-category-panel.component.js +17 -21
  65. package/dist/Actions/components/explorer/new-category-panel.component.js.map +1 -1
  66. package/dist/Actions/components/scheduled-actions.component.js +2 -2
  67. package/dist/Actions/components/scheduled-actions.component.js.map +1 -1
  68. package/dist/Actions/components/security-permissions.component.js +2 -2
  69. package/dist/Actions/components/security-permissions.component.js.map +1 -1
  70. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts +13 -5
  71. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
  72. package/dist/ComponentStudio/component-studio-dashboard.component.js +168 -145
  73. package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
  74. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts +4 -5
  75. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
  76. package/dist/ComponentStudio/components/artifact-load-dialog.component.js +197 -200
  77. package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
  78. package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts +5 -7
  79. package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts.map +1 -1
  80. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +142 -148
  81. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
  82. package/dist/ComponentStudio/components/browser/component-browser.component.js +153 -166
  83. package/dist/ComponentStudio/components/browser/component-browser.component.js.map +1 -1
  84. package/dist/ComponentStudio/components/editors/code-editor-panel.component.js +15 -20
  85. package/dist/ComponentStudio/components/editors/code-editor-panel.component.js.map +1 -1
  86. package/dist/ComponentStudio/components/editors/data-requirements-editor.component.js +16 -21
  87. package/dist/ComponentStudio/components/editors/data-requirements-editor.component.js.map +1 -1
  88. package/dist/ComponentStudio/components/editors/requirements-editor.component.js +18 -23
  89. package/dist/ComponentStudio/components/editors/requirements-editor.component.js.map +1 -1
  90. package/dist/ComponentStudio/components/editors/spec-editor.component.js +25 -30
  91. package/dist/ComponentStudio/components/editors/spec-editor.component.js.map +1 -1
  92. package/dist/ComponentStudio/components/new-component-dialog/new-component-dialog.component.js +10 -11
  93. package/dist/ComponentStudio/components/new-component-dialog/new-component-dialog.component.js.map +1 -1
  94. package/dist/ComponentStudio/components/save-version-dialog/save-version-dialog.component.d.ts.map +1 -1
  95. package/dist/ComponentStudio/components/save-version-dialog/save-version-dialog.component.js +24 -35
  96. package/dist/ComponentStudio/components/save-version-dialog/save-version-dialog.component.js.map +1 -1
  97. package/dist/ComponentStudio/components/text-import-dialog.component.js +15 -17
  98. package/dist/ComponentStudio/components/text-import-dialog.component.js.map +1 -1
  99. package/dist/Credentials/components/credentials-categories-resource.component.js +7 -6
  100. package/dist/Credentials/components/credentials-categories-resource.component.js.map +1 -1
  101. package/dist/Credentials/components/credentials-list-resource.component.js +6 -5
  102. package/dist/Credentials/components/credentials-list-resource.component.js.map +1 -1
  103. package/dist/Credentials/components/credentials-types-resource.component.js +7 -6
  104. package/dist/Credentials/components/credentials-types-resource.component.js.map +1 -1
  105. package/dist/DashboardBrowser/dashboard-share-dialog.component.js +9 -9
  106. package/dist/DashboardBrowser/dashboard-share-dialog.component.js.map +1 -1
  107. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
  108. package/dist/DataExplorer/data-explorer-dashboard.component.js +17 -17
  109. package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
  110. package/dist/Home/home-dashboard.component.js +4 -4
  111. package/dist/Home/home-dashboard.component.js.map +1 -1
  112. package/dist/Integration/components/activity/activity.component.d.ts.map +1 -1
  113. package/dist/Integration/components/activity/activity.component.js +1 -0
  114. package/dist/Integration/components/activity/activity.component.js.map +1 -1
  115. package/dist/Integration/components/connections/connections.component.d.ts.map +1 -1
  116. package/dist/Integration/components/connections/connections.component.js +5 -4
  117. package/dist/Integration/components/connections/connections.component.js.map +1 -1
  118. package/dist/Integration/components/mapping-workspace/mapping-workspace.component.d.ts.map +1 -1
  119. package/dist/Integration/components/mapping-workspace/mapping-workspace.component.js +247 -259
  120. package/dist/Integration/components/mapping-workspace/mapping-workspace.component.js.map +1 -1
  121. package/dist/Integration/components/overview/overview.component.d.ts.map +1 -1
  122. package/dist/Integration/components/overview/overview.component.js +1 -0
  123. package/dist/Integration/components/overview/overview.component.js.map +1 -1
  124. package/dist/Integration/components/pipelines/pipelines.component.d.ts.map +1 -1
  125. package/dist/Integration/components/pipelines/pipelines.component.js +1 -0
  126. package/dist/Integration/components/pipelines/pipelines.component.js.map +1 -1
  127. package/dist/Integration/components/schedules/schedules.component.d.ts.map +1 -1
  128. package/dist/Integration/components/schedules/schedules.component.js +1 -0
  129. package/dist/Integration/components/schedules/schedules.component.js.map +1 -1
  130. package/dist/Integration/components/widgets/integration-card.component.js +7 -9
  131. package/dist/Integration/components/widgets/integration-card.component.js.map +1 -1
  132. package/dist/Integration/integration.module.d.ts +6 -10
  133. package/dist/Integration/integration.module.d.ts.map +1 -1
  134. package/dist/Integration/integration.module.js +12 -20
  135. package/dist/Integration/integration.module.js.map +1 -1
  136. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.d.ts +411 -0
  137. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.d.ts.map +1 -0
  138. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.js +4266 -0
  139. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.js.map +1 -0
  140. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.d.ts +140 -0
  141. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.d.ts.map +1 -0
  142. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.js +780 -0
  143. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.js.map +1 -0
  144. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.d.ts +8 -2
  145. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.d.ts.map +1 -1
  146. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +246 -195
  147. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -1
  148. package/dist/KnowledgeHub/components/scheduling/scheduling-resource.component.d.ts +75 -0
  149. package/dist/KnowledgeHub/components/scheduling/scheduling-resource.component.d.ts.map +1 -0
  150. package/dist/KnowledgeHub/components/scheduling/scheduling-resource.component.js +601 -0
  151. package/dist/KnowledgeHub/components/scheduling/scheduling-resource.component.js.map +1 -0
  152. package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.d.ts +93 -12
  153. package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.d.ts.map +1 -1
  154. package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.js +637 -107
  155. package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.js.map +1 -1
  156. package/dist/KnowledgeHub/index.d.ts +3 -0
  157. package/dist/KnowledgeHub/index.d.ts.map +1 -1
  158. package/dist/KnowledgeHub/index.js +3 -0
  159. package/dist/KnowledgeHub/index.js.map +1 -1
  160. package/dist/Lists/components/lists-browse-resource.component.d.ts.map +1 -1
  161. package/dist/Lists/components/lists-browse-resource.component.js +9 -7
  162. package/dist/Lists/components/lists-browse-resource.component.js.map +1 -1
  163. package/dist/Lists/components/lists-my-lists-resource.component.js +5 -4
  164. package/dist/Lists/components/lists-my-lists-resource.component.js.map +1 -1
  165. package/dist/Lists/components/lists-operations-resource.component.js +10 -9
  166. package/dist/Lists/components/lists-operations-resource.component.js.map +1 -1
  167. package/dist/MCP/components/mcp-connection-dialog.component.js +141 -132
  168. package/dist/MCP/components/mcp-connection-dialog.component.js.map +1 -1
  169. package/dist/MCP/components/mcp-log-detail-panel.component.js +4 -4
  170. package/dist/MCP/components/mcp-log-detail-panel.component.js.map +1 -1
  171. package/dist/MCP/components/mcp-server-dialog.component.js +141 -128
  172. package/dist/MCP/components/mcp-server-dialog.component.js.map +1 -1
  173. package/dist/MCP/components/mcp-test-tool-dialog.component.js +210 -218
  174. package/dist/MCP/components/mcp-test-tool-dialog.component.js.map +1 -1
  175. package/dist/MCP/mcp-dashboard.component.js +2 -2
  176. package/dist/MCP/mcp-dashboard.component.js.map +1 -1
  177. package/dist/MCP/mcp.module.d.ts +6 -9
  178. package/dist/MCP/mcp.module.d.ts.map +1 -1
  179. package/dist/MCP/mcp.module.js +20 -22
  180. package/dist/MCP/mcp.module.js.map +1 -1
  181. package/dist/Scheduling/components/scheduling-activity.component.js +5 -4
  182. package/dist/Scheduling/components/scheduling-activity.component.js.map +1 -1
  183. package/dist/Scheduling/components/scheduling-jobs.component.js +6 -5
  184. package/dist/Scheduling/components/scheduling-jobs.component.js.map +1 -1
  185. package/dist/Scheduling/components/scheduling-overview.component.js +93 -92
  186. package/dist/Scheduling/components/scheduling-overview.component.js.map +1 -1
  187. package/dist/Testing/testing-dashboard.component.js +9 -10
  188. package/dist/Testing/testing-dashboard.component.js.map +1 -1
  189. package/dist/__tests__/analytics-resource.test.d.ts +2 -0
  190. package/dist/__tests__/analytics-resource.test.d.ts.map +1 -0
  191. package/dist/__tests__/analytics-resource.test.js +181 -0
  192. package/dist/__tests__/analytics-resource.test.js.map +1 -0
  193. package/dist/__tests__/scheduling.test.d.ts +2 -0
  194. package/dist/__tests__/scheduling.test.d.ts.map +1 -0
  195. package/dist/__tests__/scheduling.test.js +205 -0
  196. package/dist/__tests__/scheduling.test.js.map +1 -0
  197. package/dist/actions-dashboards.module.d.ts +8 -13
  198. package/dist/actions-dashboards.module.d.ts.map +1 -1
  199. package/dist/actions-dashboards.module.js +6 -27
  200. package/dist/actions-dashboards.module.js.map +1 -1
  201. package/dist/ai-dashboards.module.d.ts +20 -20
  202. package/dist/ai-dashboards.module.d.ts.map +1 -1
  203. package/dist/ai-dashboards.module.js +43 -44
  204. package/dist/ai-dashboards.module.js.map +1 -1
  205. package/dist/communication-dashboards.module.d.ts +4 -8
  206. package/dist/communication-dashboards.module.d.ts.map +1 -1
  207. package/dist/communication-dashboards.module.js +0 -19
  208. package/dist/communication-dashboards.module.js.map +1 -1
  209. package/dist/component-studio-dashboards.module.d.ts +7 -11
  210. package/dist/component-studio-dashboards.module.d.ts.map +1 -1
  211. package/dist/component-studio-dashboards.module.js +22 -34
  212. package/dist/component-studio-dashboards.module.js.map +1 -1
  213. package/dist/core-dashboards.module.d.ts +12 -18
  214. package/dist/core-dashboards.module.d.ts.map +1 -1
  215. package/dist/core-dashboards.module.js +15 -31
  216. package/dist/core-dashboards.module.js.map +1 -1
  217. package/dist/credentials-dashboards.module.d.ts +5 -8
  218. package/dist/credentials-dashboards.module.d.ts.map +1 -1
  219. package/dist/credentials-dashboards.module.js +3 -19
  220. package/dist/credentials-dashboards.module.js.map +1 -1
  221. package/dist/data-explorer-dashboards.module.d.ts +7 -13
  222. package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
  223. package/dist/data-explorer-dashboards.module.js +0 -27
  224. package/dist/data-explorer-dashboards.module.js.map +1 -1
  225. package/dist/lists-dashboards.module.d.ts +5 -8
  226. package/dist/lists-dashboards.module.d.ts.map +1 -1
  227. package/dist/lists-dashboards.module.js +3 -19
  228. package/dist/lists-dashboards.module.js.map +1 -1
  229. package/dist/public-api.d.ts +2 -0
  230. package/dist/public-api.d.ts.map +1 -1
  231. package/dist/public-api.js +2 -0
  232. package/dist/public-api.js.map +1 -1
  233. package/dist/scheduling-dashboards.module.d.ts +6 -10
  234. package/dist/scheduling-dashboards.module.d.ts.map +1 -1
  235. package/dist/scheduling-dashboards.module.js +3 -23
  236. package/dist/scheduling-dashboards.module.js.map +1 -1
  237. package/dist/shared/entity-field-display.d.ts +44 -0
  238. package/dist/shared/entity-field-display.d.ts.map +1 -0
  239. package/dist/shared/entity-field-display.js +118 -0
  240. package/dist/shared/entity-field-display.js.map +1 -0
  241. package/dist/testing-dashboards.module.d.ts +7 -13
  242. package/dist/testing-dashboards.module.d.ts.map +1 -1
  243. package/dist/testing-dashboards.module.js +0 -27
  244. package/dist/testing-dashboards.module.js.map +1 -1
  245. package/package.json +48 -55
@@ -1 +1 @@
1
- {"version":3,"file":"scheduling-overview.component.js","sourceRoot":"","sources":["../../../src/Scheduling/components/scheduling-overview.component.ts","../../../src/Scheduling/components/scheduling-overview.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAwC,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACtI,OAAO,EAAgB,QAAQ,EAAE,MAAM,MAAM,CAAC;;;;;;ICc1C,8BAA+B;IAC7B,gCAAmE;IACrE,iBAAM;;;IAkFI,+BAA+B;IAC7B,wBAAiC;IACjC,4BAAM;IAAA,oCAAoB;IAC5B,AAD4B,iBAAO,EAC7B;;;IAIJ,AADF,+BAA4B,cACD;IACvB,wBAAoF;IACtF,iBAAM;IAEJ,AADF,+BAA0B,cACD;IAAA,YAAgB;IAAA,iBAAM;IAC7C,+BAAuB;IAAA,YAA4E;IACrG,AADqG,iBAAM,EACrG;IACN,+BAAuE;IACrE,YACF;IACF,AADE,iBAAM,EACF;;;;IATC,eAAoC;IAApC,mDAAoC;IAAC,+DAAuC;IAGxD,eAAgB;IAAhB,qCAAgB;IAChB,eAA4E;IAA5E,uHAA4E;IAEtE,cAAuC;IAAvC,+DAAuC;IACpE,cACF;IADE,+CACF;;;IAeA,gCAAgC;IAAA,YAAiB;IAAA,iBAAO;;;IAAxB,cAAiB;IAAjB,0CAAiB;;;IAGjD,gCAAgC;IAAA,iBAAC;IAAA,iBAAO;;;IAKxC,+BAAyC;IACvC,wBAAwC;IACxC,4BAAM;IAAA,gCAAgB;IACxB,AADwB,iBAAO,EACzB;;;;IAUF,kCAEsC;IAApC,qQAAS,kCAAyB,KAAC;IACnC,yBACF;IAAA,iBAAS;;;IAXb,+BAA8D;IAC5D,wBAAiE;IAE/D,AADF,+BAA2B,cACA;IAAA,YAAe;IAAA,iBAAM;IAC9C,+BAA2B;IAAA,YAAiB;IAC9C,AAD8C,iBAAM,EAC9C;IACN,uHAAkD;IAOpD,iBAAM;;;;IAbkB,sDAAqC;IACxD,cAAsC;IAAtC,qDAAsC;IAEd,eAAe;IAAf,oCAAe;IACb,eAAiB;IAAjB,sCAAiB;IAE9C,cAMC;IAND,2EAMC;;;IAeH,+BAA+B;IAC7B,wBAA0C;IAC1C,4BAAM;IAAA,sCAAsB;IAC9B,AAD8B,iBAAO,EAC/B;;;IAKF,AADF,AADF,+BAA2B,cACE,cACE;IAAA,YAAoB;IAAA,iBAAM;IACrD,+BAA2B;IACzB,YACA;IAAA,gCAA4B;IAAA,YAAoB;IAEpD,AADE,AADkD,iBAAO,EACnD,EACF;IACN,+BAAuJ;IACrJ,YACF;IACF,AADE,iBAAM,EACF;;;;IATyB,eAAoB;IAApB,yCAAoB;IAE7C,eACA;IADA,6EACA;IAA4B,eAAoB;IAApB,yCAAoB;IAGpB,cAAsH;IAAtH,mJAAsH;IACpJ,cACF;IADE,8EACF;;;;IA0CM,kCAA8D;IAAlC,oRAAS,iCAAuB,KAAC;IAC3D,wBAAqC;IAAC,yBACxC;IAAA,iBAAS;;;IAbb,AADF,0BAAqC,aACb;IAAA,YAAgB;IAAA,iBAAK;IAC3C,8BAAsB;IAAA,YAAiB;IAAA,iBAAK;IAC5C,8BAAsB;IAAA,YAAgC;IAAA,iBAAK;IAC3D,8BAAsB;IAAA,YAA2C;IAAA,iBAAK;IAEpE,AADF,0BAAI,gBAC2D;IAC3D,aACF;IACF,AADE,iBAAO,EACJ;IACL,2BAAI;IACF,wIAAoB;IAMxB,AADE,iBAAK,EACF;;;;IAjBD,4CAAgC;IACZ,eAAgB;IAAhB,qCAAgB;IAChB,eAAiB;IAAjB,sCAAiB;IACjB,eAAgC;IAAhC,4DAAgC;IAChC,eAA2C;IAA3C,uEAA2C;IAE/B,eAA4B;IAA5B,wCAA4B;IAC1D,cACF;IADE,qEACF;IAGA,eAIC;IAJD,2CAIC;;;IAlCX,AADF,AADF,+BAA6C,cACjB,cACC;IACvB,wBAAgC;IAAC,8BACnC;IAAA,iBAAM;IACN,gCAA0B;IAAA,YAAgB;IAC5C,AAD4C,iBAAO,EAC7C;IAKE,AADF,AADF,AADF,AADF,+BAAwB,gBACI,YACjB,UACD,UACE;IAAA,oBAAG;IAAA,iBAAK;IACZ,2BAAI;IAAA,0BAAS;IAAA,iBAAK;IAClB,2BAAI;IAAA,0BAAS;IAAA,iBAAK;IAClB,2BAAI;IAAA,oCAAmB;IAAA,iBAAK;IAC5B,2BAAI;IAAA,uBAAM;IAAA,iBAAK;IACf,sBAAS;IAEb,AADE,iBAAK,EACC;IACR,8BAAO;IACL,kJAmBC;IAIT,AADE,AADE,AADE,iBAAQ,EACF,EACJ,EACF;;;IAtCwB,eAAgB;IAAhB,yCAAgB;IAetC,gBAmBC;IAnBD,2BAmBC;;;IAnNT,AADF,AAFF,8BAA8B,aAEuC,cACxC;;IACvB,+BAA4C;IAE1C,AADA,6BAAuF,iBAMtD;IACnC,iBAAM;;IACN,+BAA2D;IAAA,YAAoB;IACjF,AADiF,iBAAM,EACjF;IAEJ,AADF,+BAA4B,cACA;IAAA,8BAAa;IAAA,iBAAM;IAE3C,AADF,gCAA0B,YAClB;IAAA,yBAAuD;IAAC,aAA+B;IAAA,iBAAO;IACpG,6BAAM;IAAA,yBAA+C;IAAC,aAA0B;IAAA,iBAAO;IACvF,6BAAM;IAAA,yBAAqD;IAAC,aAAsC;IAGxG,AADE,AADE,AADoG,iBAAO,EACrG,EACF,EACF;IAIF,AADF,AADF,gCAAsB,eACE,eACO;IACzB,yBAA0C;IAC5C,iBAAM;IAEJ,AADF,gCAAyB,eACA;IAAA,aAAwB;IAAA,iBAAM;IACrD,gCAAuB;IAAA,4BAAW;IAEtC,AADE,AADoC,iBAAM,EACpC,EACF;IAEJ,AADF,gCAAsB,eACQ;IAC1B,yBAAiC;IACnC,iBAAM;IAEJ,AADF,gCAAyB,eACA;IAAA,aAA0B;IAAA,iBAAM;IACvD,gCAAuB;IAAA,8BAAa;IAExC,AADE,AADsC,iBAAM,EACtC,EACF;IAEJ,AADF,gCAAsB,eACS;IAC3B,yBAAqC;IACvC,iBAAM;IAEJ,AADF,gCAAyB,eACA;IAAA,aAA4B;IAAA,iBAAM;IACzD,gCAAuB;IAAA,2BAAU;IAAA,iBAAM;IACvC,gCAA8E;IAC5E,aACF;IAEJ,AADE,AADE,iBAAM,EACF,EACF;IAEJ,AADF,gCAAsB,eACQ;IAC1B,yBAAgC;IAClC,iBAAM;IAEJ,AADF,gCAAyB,eACA;IAAA,aAAyB;IAAA,iBAAM;IACtD,gCAAuB;IAAA,4BAAW;IAGxC,AADE,AADE,AADoC,iBAAM,EACpC,EACF,EACF;IAMA,AADF,AADF,AAFF,gCAAyB,eAEJ,eACS,eACC;IACvB,yBAAgC;IAAC,kCACnC;IAAA,iBAAM;IACN,iCAA0B;IAAA,aAAyB;IACrD,AADqD,iBAAO,EACtD;IACN,gCAAwB;IACtB,+GAAmC;IAMnC,mIAaC;IAEL,AADE,iBAAM,EACF;IAMA,AADF,AADF,AAFF,gCAA0B,eAEL,eACyE,eAC/D;IACvB,qBAAqG;IACrG,aACF;IAAA,iBAAM;IACN,gHAAyB;IAGzB,gHAA2B;IAG7B,iBAAM;IACN,gCAAwB;IACtB,+GAA2B;IAM3B,kIAeC;IAEL,AADE,iBAAM,EACF;IAIF,AADF,AADF,gCAAmB,eACS,eACC;IACvB,yBAAwC;IAAC,iCAC3C;IAAA,iBAAM;IACN,iCAA0B;IAAA,aAA6B;IACzD,AADyD,iBAAO,EAC1D;IACN,gCAAwB;IACtB,+GAAuC;IAMvC,mIAaC;IAIT,AADE,AADE,AADE,iBAAM,EACF,EACF,EACF;IAEN,gHAAwB;IA8C1B,iBAAM;;;IA1NuB,cAAuC;IAAvC,uDAAuC;IAK1D,eAAgC;;IAMV,cAAgC;IAAhC,gDAAgC;IAAC,cAAoB;IAApB,6CAAoB;IAKf,eAA+B;IAA/B,kEAA+B;IACvC,eAA0B;IAA1B,6DAA0B;IACpB,eAAsC;IAAtC,yEAAsC;IAW3E,eAAwB;IAAxB,iDAAwB;IASxB,eAA0B;IAA1B,mDAA0B;IAS1B,eAA4B;IAA5B,qDAA4B;IAE9B,eAAwD;IAAxD,+EAAwD;IAC3E,cACF;IADE,4FACF;IAQuB,eAAyB;IAAzB,kDAAyB;IAatB,gBAAyB;IAAzB,kDAAyB;IAGnD,eAKC;IALD,8DAKC;IACD,cAaC;IAbD,oCAaC;IAOyB,eAA+D;IAA/D,oFAA+D;IAElF,eAA6F;IAA7F,yGAA6F;IAChG,cACF;IADE,kFACF;IACA,cAEC;IAFD,oDAEC;IACD,cAEC;IAFD,sDAEC;IAGD,eAKC;IALD,sDAKC;IACD,cAeC;IAfD,4BAeC;IASyB,eAA6B;IAA7B,sDAA6B;IAGvD,eAKC;IALD,kEAKC;IACD,cAaC;IAbD,wCAaC;IAMT,eA6CC;IA7CD,mDA6CC;;AD9NP,MAAM,OAAO,2BAA2B;IAgB5B;IACA;IAhBD,YAAY,GAA4B,EAAE,CAAC;IAC1C,WAAW,GAAG,IAAI,YAAY,EAA2B,CAAC;IAE7D,IAAI,GAA0B,IAAI,CAAC;IACnC,cAAc,GAAmB,EAAE,CAAC;IACpC,kBAAkB,GAAwB,EAAE,CAAC;IAC7C,KAAK,GAAe,EAAE,CAAC;IACvB,MAAM,GAAqB,EAAE,CAAC;IAC9B,SAAS,GAAG,IAAI,CAAC;IACjB,kBAAkB,GAAG,IAAI,CAAC;IAEzB,aAAa,GAAmB,EAAE,CAAC;IACnC,cAAc,CAA2B;IAEjD,YACU,iBAAmD,EACnD,GAAsB;QADtB,sBAAiB,GAAjB,iBAAiB,CAAkC;QACnD,QAAG,GAAH,GAAG,CAAmB;IAC7B,CAAC;IAEJ,QAAQ;QACN,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,IAAI,IAAI,CAAC,YAAY,CAAC,oBAAoB,CAAC,IAAI,IAAI,EAAE,CAAC;gBACpD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,YAAY,CAAC,oBAAoB,CAAY,CAAC;YAC/E,CAAC;QACH,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,IAAI,CACrB,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;YAC5C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;YACjB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC,CAAC,EACF,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;YACvD,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;YAC5B,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC,CAAC,EACF,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE;YAC9D,IAAI,CAAC,kBAAkB,GAAG,QAAQ,CAAC;YACnC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC,CAAC,EACF,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;YACjD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC,CAAC,EACF,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE;YAChD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC1B,CAAC,CAAC,CACH,CAAC;QAEF,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,WAAW;QACT,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC;IAEM,OAAO;QACZ,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;IACnC,CAAC;IAEM,iBAAiB;QACtB,IAAI,CAAC,kBAAkB,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACnD,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,KAAa;QACpC,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAEM,aAAa,CAAC,MAAc;QACjC,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,SAAS,CAAC,CAAC,OAAO,6BAA6B,CAAC;YACrD,KAAK,WAAW,CAAC,CAAC,OAAO,0BAA0B,CAAC;YACpD,KAAK,QAAQ,CAAC,CAAC,OAAO,0BAA0B,CAAC;YACjD,KAAK,WAAW,CAAC,CAAC,OAAO,iBAAiB,CAAC;YAC3C,KAAK,SAAS,CAAC,CAAC,OAAO,mBAAmB,CAAC;YAC3C,OAAO,CAAC,CAAC,OAAO,6BAA6B,CAAC;QAChD,CAAC;IACH,CAAC;IAEM,cAAc,CAAC,MAAc;QAClC,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,SAAS,CAAC,CAAC,OAAO,gBAAgB,CAAC;YACxC,KAAK,WAAW,CAAC,CAAC,OAAO,gBAAgB,CAAC;YAC1C,KAAK,QAAQ,CAAC,CAAC,OAAO,cAAc,CAAC;YACrC,KAAK,WAAW,CAAC,CAAC,OAAO,gBAAgB,CAAC;YAC1C,KAAK,SAAS,CAAC,CAAC,OAAO,gBAAgB,CAAC;YACxC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;IAEM,YAAY,CAAC,QAAgB;QAClC,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,kCAAkC,CAAC;IAChG,CAAC;IAEM,cAAc,CAAC,EAAsB;QAC1C,IAAI,EAAE,IAAI,IAAI;YAAE,OAAO,GAAG,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QACtC,IAAI,OAAO,GAAG,EAAE;YAAE,OAAO,GAAG,OAAO,GAAG,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACzC,MAAM,gBAAgB,GAAG,OAAO,GAAG,EAAE,CAAC;QACtC,IAAI,OAAO,GAAG,EAAE;YAAE,OAAO,GAAG,OAAO,KAAK,gBAAgB,GAAG,CAAC;QAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACvC,MAAM,gBAAgB,GAAG,OAAO,GAAG,EAAE,CAAC;QACtC,OAAO,GAAG,KAAK,KAAK,gBAAgB,GAAG,CAAC;IAC1C,CAAC;IAEM,aAAa,CAAC,IAAU;QAC7B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QAC1C,IAAI,OAAO,GAAG,EAAE;YAAE,OAAO,GAAG,OAAO,OAAO,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACzC,IAAI,OAAO,GAAG,EAAE;YAAE,OAAO,GAAG,OAAO,OAAO,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACxC,IAAI,MAAM,GAAG,EAAE;YAAE,OAAO,GAAG,MAAM,OAAO,CAAC;QACzC,OAAO,IAAI,CAAC,kBAAkB,EAAE,CAAC;IACnC,CAAC;IAEM,eAAe,CAAC,IAAU;QAC/B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;QAC9C,IAAI,MAAM,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;QAC3C,IAAI,OAAO,GAAG,EAAE;YAAE,OAAO,GAAG,OAAO,GAAG,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACxC,MAAM,YAAY,GAAG,OAAO,GAAG,EAAE,CAAC;QAClC,OAAO,GAAG,MAAM,KAAK,YAAY,GAAG,CAAC;IACvC,CAAC;IAEM,UAAU,CAAC,IAAY;QAC5B,IAAI,IAAI,GAAG,IAAI;YAAE,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,IAAI,IAAI,GAAG,CAAC;YAAE,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/B,CAAC;IAEM,gBAAgB,CAAC,KAAa;QACnC,OAAO,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACxC,CAAC;IAEM,cAAc,CAAC,IAAU;QAC9B,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE;YACpC,KAAK,EAAE,SAAS;YAChB,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;IACL,CAAC;IAEM,mBAAmB,CAAC,IAAY;QACrC,IAAI,IAAI,IAAI,GAAG;YAAE,OAAO,SAAS,CAAC;QAClC,IAAI,IAAI,IAAI,GAAG;YAAE,OAAO,SAAS,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IAEM,cAAc;QACnB,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO,GAAG,CAAC;QAC3B,IAAI,KAAK,GAAG,GAAG,CAAC;QAChB,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QACvC,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QACnC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;QACrE,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC;IAEM,cAAc;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACpC,IAAI,KAAK,IAAI,EAAE;YAAE,OAAO,SAAS,CAAC;QAClC,IAAI,KAAK,IAAI,EAAE;YAAE,OAAO,SAAS,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IAEM,wBAAwB;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACpC,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,aAAa,CAAC;QAC7C,OAAO,GAAG,MAAM,IAAI,aAAa,GAAG,MAAM,EAAE,CAAC;IAC/C,CAAC;IAEO,gBAAgB;QACtB,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;YACnD,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,eAAe;QACrB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;QAClC,CAAC;IACH,CAAC;IAEO,SAAS;QACf,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;YACpB,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;SAC5C,CAAC,CAAC;IACL,CAAC;qHAjNU,2BAA2B;6DAA3B,2BAA2B;YCdlC,AADF,AADF,AAFF,8BAAgC,aAED,aACE,gBACqB;YAApB,wGAAS,aAAS,IAAC;YAC7C,uBAAyC;YAAC,yBAC5C;YAAA,iBAAS;YACT,iCAA8F;YAA9B,wGAAS,uBAAmB,IAAC;YAC3F,uBAAkC;YAClC,YACF;YAEJ,AADE,AADE,iBAAS,EACL,EACF;YAEN,6FAAiB;YAMjB,iGAA0B;YA+N5B,iBAAM;;YA5O4B,eAAmC;YAAnC,gDAAmC;YAE7D,eACF;YADE,oFACF;YAIJ,cAIC;YAJD,wCAIC;YAED,cA8NC;YA9ND,sDA8NC;;;iFDhOU,2BAA2B;cAPvC,SAAS;6BACI,KAAK,YACP,yBAAyB,mBAGlB,uBAAuB,CAAC,MAAM;;kBAG9C,KAAK;;kBACL,MAAM;;kFAFI,2BAA2B","sourcesContent":["import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';\nimport { Subscription, interval } from 'rxjs';\nimport {\n SchedulingInstrumentationService,\n SchedulingKPIs,\n JobExecution,\n UpcomingExecution,\n LockInfo,\n AlertCondition\n} from '../services/scheduling-instrumentation.service';\n\n@Component({\n standalone: false,\n selector: 'app-scheduling-overview',\n templateUrl: './scheduling-overview.component.html',\n styleUrls: ['./scheduling-overview.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SchedulingOverviewComponent implements OnInit, OnDestroy {\n @Input() initialState: Record<string, unknown> = {};\n @Output() stateChange = new EventEmitter<Record<string, unknown>>();\n\n public Kpis: SchedulingKPIs | null = null;\n public LiveExecutions: JobExecution[] = [];\n public UpcomingExecutions: UpcomingExecution[] = [];\n public Locks: LockInfo[] = [];\n public Alerts: AlertCondition[] = [];\n public IsLoading = true;\n public AutoRefreshEnabled = true;\n\n private subscriptions: Subscription[] = [];\n private autoRefreshSub: Subscription | undefined;\n\n constructor(\n private schedulingService: SchedulingInstrumentationService,\n private cdr: ChangeDetectorRef\n ) {}\n\n ngOnInit(): void {\n if (this.initialState) {\n if (this.initialState['autoRefreshEnabled'] != null) {\n this.AutoRefreshEnabled = this.initialState['autoRefreshEnabled'] as boolean;\n }\n }\n\n this.subscriptions.push(\n this.schedulingService.kpis$.subscribe(kpis => {\n this.Kpis = kpis;\n this.IsLoading = false;\n this.cdr.markForCheck();\n }),\n this.schedulingService.liveExecutions$.subscribe(execs => {\n this.LiveExecutions = execs;\n this.cdr.markForCheck();\n }),\n this.schedulingService.upcomingExecutions$.subscribe(upcoming => {\n this.UpcomingExecutions = upcoming;\n this.cdr.markForCheck();\n }),\n this.schedulingService.lockInfo$.subscribe(locks => {\n this.Locks = locks;\n this.cdr.markForCheck();\n }),\n this.schedulingService.alerts$.subscribe(alerts => {\n this.Alerts = alerts;\n this.cdr.markForCheck();\n })\n );\n\n if (this.AutoRefreshEnabled) {\n this.startAutoRefresh();\n }\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach(s => s.unsubscribe());\n this.stopAutoRefresh();\n }\n\n public Refresh(): void {\n this.schedulingService.refresh();\n }\n\n public ToggleAutoRefresh(): void {\n this.AutoRefreshEnabled = !this.AutoRefreshEnabled;\n if (this.AutoRefreshEnabled) {\n this.startAutoRefresh();\n } else {\n this.stopAutoRefresh();\n }\n this.emitState();\n }\n\n public async ReleaseLock(jobId: string): Promise<void> {\n await this.schedulingService.releaseLock(jobId);\n }\n\n public GetStatusIcon(status: string): string {\n switch (status) {\n case 'Running': return 'fa-solid fa-spinner fa-spin';\n case 'Completed': return 'fa-solid fa-circle-check';\n case 'Failed': return 'fa-solid fa-circle-xmark';\n case 'Cancelled': return 'fa-solid fa-ban';\n case 'Timeout': return 'fa-solid fa-clock';\n default: return 'fa-solid fa-circle-question';\n }\n }\n\n public GetStatusClass(status: string): string {\n switch (status) {\n case 'Running': return 'status-running';\n case 'Completed': return 'status-success';\n case 'Failed': return 'status-error';\n case 'Cancelled': return 'status-warning';\n case 'Timeout': return 'status-warning';\n default: return '';\n }\n }\n\n public GetAlertIcon(severity: string): string {\n return severity === 'error' ? 'fa-solid fa-circle-xmark' : 'fa-solid fa-triangle-exclamation';\n }\n\n public FormatDuration(ms: number | undefined): string {\n if (ms == null) return '-';\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n return `${hours}h ${remainingMinutes}m`;\n }\n\n public FormatTimeAgo(date: Date): string {\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffSec = Math.floor(diffMs / 1000);\n if (diffSec < 60) return `${diffSec}s ago`;\n const diffMin = Math.floor(diffSec / 60);\n if (diffMin < 60) return `${diffMin}m ago`;\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24) return `${diffHr}h ago`;\n return date.toLocaleDateString();\n }\n\n public FormatTimeUntil(date: Date): string {\n const now = new Date();\n const diffMs = date.getTime() - now.getTime();\n if (diffMs <= 0) return 'Now';\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 60) return `${diffMin}m`;\n const diffHr = Math.floor(diffMin / 60);\n const remainingMin = diffMin % 60;\n return `${diffHr}h ${remainingMin}m`;\n }\n\n public FormatCost(cost: number): string {\n if (cost < 0.01) return `$${cost.toFixed(4)}`;\n if (cost < 1) return `$${cost.toFixed(3)}`;\n return `$${cost.toFixed(2)}`;\n }\n\n public FormatPercentage(value: number): string {\n return `${(value * 100).toFixed(1)}%`;\n }\n\n public FormatDateTime(date: Date): string {\n return date.toLocaleString(undefined, {\n month: 'numeric',\n day: 'numeric',\n year: '2-digit',\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n });\n }\n\n public GetSuccessRateColor(rate: number): string {\n if (rate >= 0.9) return '#10b981';\n if (rate >= 0.7) return '#f59e0b';\n return '#ef4444';\n }\n\n public GetHealthScore(): number {\n if (!this.Kpis) return 100;\n let score = 100;\n score -= this.Kpis.totalFailures7d * 2;\n score -= this.Kpis.lockedJobs * 10;\n score -= this.Alerts.filter(a => a.severity === 'error').length * 15;\n score -= this.Alerts.filter(a => a.severity === 'warning').length * 5;\n return Math.max(0, Math.min(100, score));\n }\n\n public GetHealthColor(): string {\n const score = this.GetHealthScore();\n if (score >= 80) return '#10b981';\n if (score >= 50) return '#f59e0b';\n return '#ef4444';\n }\n\n public GetHealthStrokeDasharray(): string {\n const score = this.GetHealthScore();\n const circumference = 2 * Math.PI * 36;\n const filled = (score / 100) * circumference;\n return `${filled} ${circumference - filled}`;\n }\n\n private startAutoRefresh(): void {\n this.stopAutoRefresh();\n this.autoRefreshSub = interval(30000).subscribe(() => {\n this.schedulingService.refresh();\n });\n }\n\n private stopAutoRefresh(): void {\n if (this.autoRefreshSub) {\n this.autoRefreshSub.unsubscribe();\n this.autoRefreshSub = undefined;\n }\n }\n\n private emitState(): void {\n this.stateChange.emit({\n autoRefreshEnabled: this.AutoRefreshEnabled\n });\n }\n}\n","<div class=\"overview-container\">\n <!-- Header Controls -->\n <div class=\"overview-header\">\n <div class=\"header-controls\">\n <button class=\"control-btn\" (click)=\"Refresh()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i> Refresh\n </button>\n <button class=\"control-btn\" [class.active]=\"AutoRefreshEnabled\" (click)=\"ToggleAutoRefresh()\">\n <i class=\"fa-solid fa-rotate\"></i>\n Auto-Refresh: {{AutoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n </div>\n </div>\n\n @if (IsLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading dashboard...\" size=\"medium\"></mj-loading>\n </div>\n }\n\n @if (!IsLoading && Kpis) {\n <div class=\"overview-content\">\n <!-- Health Banner -->\n <div class=\"health-banner\" [style.border-color]=\"GetHealthColor()\">\n <div class=\"health-ring\">\n <svg viewBox=\"0 0 80 80\" class=\"health-svg\">\n <circle cx=\"40\" cy=\"40\" r=\"36\" fill=\"none\" class=\"health-ring-track\" stroke-width=\"6\"/>\n <circle cx=\"40\" cy=\"40\" r=\"36\" fill=\"none\"\n [attr.stroke]=\"GetHealthColor()\"\n stroke-width=\"6\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"GetHealthStrokeDasharray()\"\n transform=\"rotate(-90 40 40)\"/>\n </svg>\n <div class=\"health-score\" [style.color]=\"GetHealthColor()\">{{GetHealthScore()}}</div>\n </div>\n <div class=\"health-details\">\n <div class=\"health-title\">System Health</div>\n <div class=\"health-stats\">\n <span><i class=\"fa-solid fa-check-circle status-success\"></i> {{Kpis.totalActiveJobs}} active</span>\n <span><i class=\"fa-solid fa-lock status-warning\"></i> {{Kpis.lockedJobs}} locked</span>\n <span><i class=\"fa-solid fa-xmark-circle status-error\"></i> {{Kpis.totalFailures7d}} failures (7d)</span>\n </div>\n </div>\n </div>\n <!-- KPI Cards -->\n <div class=\"kpi-grid\">\n <div class=\"kpi-card\">\n <div class=\"kpi-icon blue\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{Kpis.totalActiveJobs}}</div>\n <div class=\"kpi-label\">Active Jobs</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon amber\">\n <i class=\"fa-solid fa-clock\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{Kpis.jobsDueInNextHour}}</div>\n <div class=\"kpi-label\">Due Next Hour</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon purple\">\n <i class=\"fa-solid fa-chart-bar\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{Kpis.recentExecutions24h}}</div>\n <div class=\"kpi-label\">Runs (24h)</div>\n <div class=\"kpi-sub\" [style.color]=\"GetSuccessRateColor(Kpis.successRate24h)\">\n {{FormatPercentage(Kpis.successRate24h)}} success\n </div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon green\">\n <i class=\"fa-solid fa-play\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{Kpis.currentlyRunning}}</div>\n <div class=\"kpi-label\">Running Now</div>\n </div>\n </div>\n </div>\n <!-- Two-Column Layout: Live Executions + Alerts/Upcoming -->\n <div class=\"panels-grid\">\n <!-- Live Executions -->\n <div class=\"panel\">\n <div class=\"panel-header\">\n <div class=\"panel-title\">\n <i class=\"fa-solid fa-bolt\"></i> Live Executions\n </div>\n <span class=\"panel-badge\">{{LiveExecutions.length}}</span>\n </div>\n <div class=\"panel-body\">\n @if (LiveExecutions.length === 0) {\n <div class=\"empty-state-small\">\n <i class=\"fa-solid fa-inbox\"></i>\n <span>No recent executions</span>\n </div>\n }\n @for (exec of LiveExecutions; track exec) {\n <div class=\"execution-item\">\n <div class=\"exec-status\">\n <i [class]=\"GetStatusIcon(exec.status)\" [ngClass]=\"GetStatusClass(exec.status)\"></i>\n </div>\n <div class=\"exec-details\">\n <div class=\"exec-name\">{{exec.jobName}}</div>\n <div class=\"exec-meta\">{{FormatTimeAgo(exec.startedAt)}} &middot; {{FormatDuration(exec.duration)}}</div>\n </div>\n <div class=\"exec-status-badge\" [ngClass]=\"GetStatusClass(exec.status)\">\n {{exec.status}}\n </div>\n </div>\n }\n </div>\n </div>\n <!-- Right Column: Alerts + Upcoming -->\n <div class=\"right-column\">\n <!-- Alerts -->\n <div class=\"panel\">\n <div class=\"panel-header\" [ngClass]=\"Alerts.length > 0 ? 'alert-header' : 'clear-header'\">\n <div class=\"panel-title\">\n <i [class]=\"Alerts.length > 0 ? 'fa-solid fa-triangle-exclamation' : 'fa-solid fa-shield-check'\"></i>\n {{Alerts.length > 0 ? 'Alerts' : 'All Clear'}}\n </div>\n @if (Alerts.length > 0) {\n <span class=\"panel-badge error\">{{Alerts.length}}</span>\n }\n @if (Alerts.length === 0) {\n <span class=\"panel-badge clear\">0</span>\n }\n </div>\n <div class=\"panel-body\">\n @if (Alerts.length === 0) {\n <div class=\"empty-state-small all-clear\">\n <i class=\"fa-solid fa-circle-check\"></i>\n <span>No active alerts</span>\n </div>\n }\n @for (alert of Alerts; track alert) {\n <div class=\"alert-item\" [ngClass]=\"'alert-' + alert.severity\">\n <i [class]=\"GetAlertIcon(alert.severity)\" class=\"alert-icon\"></i>\n <div class=\"alert-content\">\n <div class=\"alert-title\">{{alert.title}}</div>\n <div class=\"alert-message\">{{alert.message}}</div>\n </div>\n @if (alert.type === 'stale-lock' && alert.jobId) {\n <button\n class=\"alert-action-btn\"\n (click)=\"ReleaseLock(alert.jobId!)\">\n Release\n </button>\n }\n </div>\n }\n </div>\n </div>\n <!-- Upcoming -->\n <div class=\"panel\">\n <div class=\"panel-header\">\n <div class=\"panel-title\">\n <i class=\"fa-solid fa-calendar-day\"></i> Upcoming (24h)\n </div>\n <span class=\"panel-badge\">{{UpcomingExecutions.length}}</span>\n </div>\n <div class=\"panel-body\">\n @if (UpcomingExecutions.length === 0) {\n <div class=\"empty-state-small\">\n <i class=\"fa-solid fa-calendar-xmark\"></i>\n <span>No upcoming executions</span>\n </div>\n }\n @for (upcoming of UpcomingExecutions; track upcoming) {\n <div class=\"upcoming-item\">\n <div class=\"upcoming-info\">\n <div class=\"upcoming-name\">{{upcoming.jobName}}</div>\n <div class=\"upcoming-meta\">\n {{FormatDateTime(upcoming.nextRunAt)}}\n <span class=\"upcoming-type\">{{upcoming.jobType}}</span>\n </div>\n </div>\n <div class=\"upcoming-countdown\" [class.soon]=\"FormatTimeUntil(upcoming.nextRunAt).includes('m') && !FormatTimeUntil(upcoming.nextRunAt).includes('h')\">\n {{FormatTimeUntil(upcoming.nextRunAt)}}\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n <!-- Locks Section -->\n @if (Locks.length > 0) {\n <div class=\"panel\" style=\"margin-top: 20px;\">\n <div class=\"panel-header\">\n <div class=\"panel-title\">\n <i class=\"fa-solid fa-lock\"></i> Active Locks\n </div>\n <span class=\"panel-badge\">{{Locks.length}}</span>\n </div>\n <div class=\"panel-body\">\n <table class=\"data-table\">\n <thead>\n <tr>\n <th>Job</th>\n <th>Locked By</th>\n <th>Locked At</th>\n <th>Expected Completion</th>\n <th>Status</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (lock of Locks; track lock) {\n <tr [class.stale-row]=\"lock.isStale\">\n <td class=\"cell-name\">{{lock.jobName}}</td>\n <td class=\"cell-meta\">{{lock.lockedBy}}</td>\n <td class=\"cell-meta\">{{FormatTimeAgo(lock.lockedAt)}}</td>\n <td class=\"cell-meta\">{{FormatDateTime(lock.expectedCompletion)}}</td>\n <td>\n <span class=\"lock-status-badge\" [class.stale]=\"lock.isStale\">\n {{lock.isStale ? 'Stale' : 'Active'}}\n </span>\n </td>\n <td>\n @if (lock.isStale) {\n <button class=\"release-btn\" (click)=\"ReleaseLock(lock.jobId)\">\n <i class=\"fa-solid fa-lock-open\"></i> Release\n </button>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n </div>\n }\n</div>\n"]}
1
+ {"version":3,"file":"scheduling-overview.component.js","sourceRoot":"","sources":["../../../src/Scheduling/components/scheduling-overview.component.ts","../../../src/Scheduling/components/scheduling-overview.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';\nimport { Subscription, interval } from 'rxjs';\nimport {\n SchedulingInstrumentationService,\n SchedulingKPIs,\n JobExecution,\n UpcomingExecution,\n LockInfo,\n AlertCondition\n} from '../services/scheduling-instrumentation.service';\n\n@Component({\n standalone: false,\n selector: 'app-scheduling-overview',\n templateUrl: './scheduling-overview.component.html',\n styleUrls: ['./scheduling-overview.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SchedulingOverviewComponent implements OnInit, OnDestroy {\n @Input() initialState: Record<string, unknown> = {};\n @Output() stateChange = new EventEmitter<Record<string, unknown>>();\n\n public Kpis: SchedulingKPIs | null = null;\n public LiveExecutions: JobExecution[] = [];\n public UpcomingExecutions: UpcomingExecution[] = [];\n public Locks: LockInfo[] = [];\n public Alerts: AlertCondition[] = [];\n public IsLoading = true;\n public AutoRefreshEnabled = true;\n\n private subscriptions: Subscription[] = [];\n private autoRefreshSub: Subscription | undefined;\n\n constructor(\n private schedulingService: SchedulingInstrumentationService,\n private cdr: ChangeDetectorRef\n ) {}\n\n ngOnInit(): void {\n if (this.initialState) {\n if (this.initialState['autoRefreshEnabled'] != null) {\n this.AutoRefreshEnabled = this.initialState['autoRefreshEnabled'] as boolean;\n }\n }\n\n this.subscriptions.push(\n this.schedulingService.kpis$.subscribe(kpis => {\n this.Kpis = kpis;\n this.IsLoading = false;\n this.cdr.markForCheck();\n }),\n this.schedulingService.liveExecutions$.subscribe(execs => {\n this.LiveExecutions = execs;\n this.cdr.markForCheck();\n }),\n this.schedulingService.upcomingExecutions$.subscribe(upcoming => {\n this.UpcomingExecutions = upcoming;\n this.cdr.markForCheck();\n }),\n this.schedulingService.lockInfo$.subscribe(locks => {\n this.Locks = locks;\n this.cdr.markForCheck();\n }),\n this.schedulingService.alerts$.subscribe(alerts => {\n this.Alerts = alerts;\n this.cdr.markForCheck();\n })\n );\n\n if (this.AutoRefreshEnabled) {\n this.startAutoRefresh();\n }\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach(s => s.unsubscribe());\n this.stopAutoRefresh();\n }\n\n public Refresh(): void {\n this.schedulingService.refresh();\n }\n\n public ToggleAutoRefresh(): void {\n this.AutoRefreshEnabled = !this.AutoRefreshEnabled;\n if (this.AutoRefreshEnabled) {\n this.startAutoRefresh();\n } else {\n this.stopAutoRefresh();\n }\n this.emitState();\n }\n\n public async ReleaseLock(jobId: string): Promise<void> {\n await this.schedulingService.releaseLock(jobId);\n }\n\n public GetStatusIcon(status: string): string {\n switch (status) {\n case 'Running': return 'fa-solid fa-spinner fa-spin';\n case 'Completed': return 'fa-solid fa-circle-check';\n case 'Failed': return 'fa-solid fa-circle-xmark';\n case 'Cancelled': return 'fa-solid fa-ban';\n case 'Timeout': return 'fa-solid fa-clock';\n default: return 'fa-solid fa-circle-question';\n }\n }\n\n public GetStatusClass(status: string): string {\n switch (status) {\n case 'Running': return 'status-running';\n case 'Completed': return 'status-success';\n case 'Failed': return 'status-error';\n case 'Cancelled': return 'status-warning';\n case 'Timeout': return 'status-warning';\n default: return '';\n }\n }\n\n public GetAlertIcon(severity: string): string {\n return severity === 'error' ? 'fa-solid fa-circle-xmark' : 'fa-solid fa-triangle-exclamation';\n }\n\n public FormatDuration(ms: number | undefined): string {\n if (ms == null) return '-';\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n return `${hours}h ${remainingMinutes}m`;\n }\n\n public FormatTimeAgo(date: Date): string {\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffSec = Math.floor(diffMs / 1000);\n if (diffSec < 60) return `${diffSec}s ago`;\n const diffMin = Math.floor(diffSec / 60);\n if (diffMin < 60) return `${diffMin}m ago`;\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24) return `${diffHr}h ago`;\n return date.toLocaleDateString();\n }\n\n public FormatTimeUntil(date: Date): string {\n const now = new Date();\n const diffMs = date.getTime() - now.getTime();\n if (diffMs <= 0) return 'Now';\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 60) return `${diffMin}m`;\n const diffHr = Math.floor(diffMin / 60);\n const remainingMin = diffMin % 60;\n return `${diffHr}h ${remainingMin}m`;\n }\n\n public FormatCost(cost: number): string {\n if (cost < 0.01) return `$${cost.toFixed(4)}`;\n if (cost < 1) return `$${cost.toFixed(3)}`;\n return `$${cost.toFixed(2)}`;\n }\n\n public FormatPercentage(value: number): string {\n return `${(value * 100).toFixed(1)}%`;\n }\n\n public FormatDateTime(date: Date): string {\n return date.toLocaleString(undefined, {\n month: 'numeric',\n day: 'numeric',\n year: '2-digit',\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n });\n }\n\n public GetSuccessRateColor(rate: number): string {\n if (rate >= 0.9) return '#10b981';\n if (rate >= 0.7) return '#f59e0b';\n return '#ef4444';\n }\n\n public GetHealthScore(): number {\n if (!this.Kpis) return 100;\n let score = 100;\n score -= this.Kpis.totalFailures7d * 2;\n score -= this.Kpis.lockedJobs * 10;\n score -= this.Alerts.filter(a => a.severity === 'error').length * 15;\n score -= this.Alerts.filter(a => a.severity === 'warning').length * 5;\n return Math.max(0, Math.min(100, score));\n }\n\n public GetHealthColor(): string {\n const score = this.GetHealthScore();\n if (score >= 80) return '#10b981';\n if (score >= 50) return '#f59e0b';\n return '#ef4444';\n }\n\n public GetHealthStrokeDasharray(): string {\n const score = this.GetHealthScore();\n const circumference = 2 * Math.PI * 36;\n const filled = (score / 100) * circumference;\n return `${filled} ${circumference - filled}`;\n }\n\n private startAutoRefresh(): void {\n this.stopAutoRefresh();\n this.autoRefreshSub = interval(30000).subscribe(() => {\n this.schedulingService.refresh();\n });\n }\n\n private stopAutoRefresh(): void {\n if (this.autoRefreshSub) {\n this.autoRefreshSub.unsubscribe();\n this.autoRefreshSub = undefined;\n }\n }\n\n private emitState(): void {\n this.stateChange.emit({\n autoRefreshEnabled: this.AutoRefreshEnabled\n });\n }\n}\n","<div class=\"overview-container\">\n <!-- Header Controls -->\n <div class=\"overview-header\">\n <div class=\"header-controls\">\n <button mjButton variant=\"secondary\" (click)=\"Refresh()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i> Refresh\n </button>\n <button mjButton [variant]=\"AutoRefreshEnabled ? 'primary' : 'secondary'\" (click)=\"ToggleAutoRefresh()\">\n <i class=\"fa-solid fa-rotate\"></i>\n Auto-Refresh: {{AutoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n </div>\n </div>\n\n @if (IsLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading dashboard...\" size=\"medium\"></mj-loading>\n </div>\n }\n\n @if (!IsLoading && Kpis) {\n <div class=\"overview-content\">\n <!-- Health Banner -->\n <div class=\"health-banner\" [style.border-color]=\"GetHealthColor()\">\n <div class=\"health-ring\">\n <svg viewBox=\"0 0 80 80\" class=\"health-svg\">\n <circle cx=\"40\" cy=\"40\" r=\"36\" fill=\"none\" class=\"health-ring-track\" stroke-width=\"6\"/>\n <circle cx=\"40\" cy=\"40\" r=\"36\" fill=\"none\"\n [attr.stroke]=\"GetHealthColor()\"\n stroke-width=\"6\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"GetHealthStrokeDasharray()\"\n transform=\"rotate(-90 40 40)\"/>\n </svg>\n <div class=\"health-score\" [style.color]=\"GetHealthColor()\">{{GetHealthScore()}}</div>\n </div>\n <div class=\"health-details\">\n <div class=\"health-title\">System Health</div>\n <div class=\"health-stats\">\n <span><i class=\"fa-solid fa-check-circle status-success\"></i> {{Kpis.totalActiveJobs}} active</span>\n <span><i class=\"fa-solid fa-lock status-warning\"></i> {{Kpis.lockedJobs}} locked</span>\n <span><i class=\"fa-solid fa-xmark-circle status-error\"></i> {{Kpis.totalFailures7d}} failures (7d)</span>\n </div>\n </div>\n </div>\n <!-- KPI Cards -->\n <div class=\"kpi-grid\">\n <div class=\"kpi-card\">\n <div class=\"kpi-icon blue\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{Kpis.totalActiveJobs}}</div>\n <div class=\"kpi-label\">Active Jobs</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon amber\">\n <i class=\"fa-solid fa-clock\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{Kpis.jobsDueInNextHour}}</div>\n <div class=\"kpi-label\">Due Next Hour</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon purple\">\n <i class=\"fa-solid fa-chart-bar\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{Kpis.recentExecutions24h}}</div>\n <div class=\"kpi-label\">Runs (24h)</div>\n <div class=\"kpi-sub\" [style.color]=\"GetSuccessRateColor(Kpis.successRate24h)\">\n {{FormatPercentage(Kpis.successRate24h)}} success\n </div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon green\">\n <i class=\"fa-solid fa-play\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{Kpis.currentlyRunning}}</div>\n <div class=\"kpi-label\">Running Now</div>\n </div>\n </div>\n </div>\n <!-- Two-Column Layout: Live Executions + Alerts/Upcoming -->\n <div class=\"panels-grid\">\n <!-- Live Executions -->\n <div class=\"panel\">\n <div class=\"panel-header\">\n <div class=\"panel-title\">\n <i class=\"fa-solid fa-bolt\"></i> Live Executions\n </div>\n <span class=\"panel-badge\">{{LiveExecutions.length}}</span>\n </div>\n <div class=\"panel-body\">\n @if (LiveExecutions.length === 0) {\n <div class=\"empty-state-small\">\n <i class=\"fa-solid fa-inbox\"></i>\n <span>No recent executions</span>\n </div>\n }\n @for (exec of LiveExecutions; track exec) {\n <div class=\"execution-item\">\n <div class=\"exec-status\">\n <i [class]=\"GetStatusIcon(exec.status)\" [ngClass]=\"GetStatusClass(exec.status)\"></i>\n </div>\n <div class=\"exec-details\">\n <div class=\"exec-name\">{{exec.jobName}}</div>\n <div class=\"exec-meta\">{{FormatTimeAgo(exec.startedAt)}} &middot; {{FormatDuration(exec.duration)}}</div>\n </div>\n <div class=\"exec-status-badge\" [ngClass]=\"GetStatusClass(exec.status)\">\n {{exec.status}}\n </div>\n </div>\n }\n </div>\n </div>\n <!-- Right Column: Alerts + Upcoming -->\n <div class=\"right-column\">\n <!-- Alerts -->\n <div class=\"panel\">\n <div class=\"panel-header\" [ngClass]=\"Alerts.length > 0 ? 'alert-header' : 'clear-header'\">\n <div class=\"panel-title\">\n <i [class]=\"Alerts.length > 0 ? 'fa-solid fa-triangle-exclamation' : 'fa-solid fa-shield-check'\"></i>\n {{Alerts.length > 0 ? 'Alerts' : 'All Clear'}}\n </div>\n @if (Alerts.length > 0) {\n <span class=\"panel-badge error\">{{Alerts.length}}</span>\n }\n @if (Alerts.length === 0) {\n <span class=\"panel-badge clear\">0</span>\n }\n </div>\n <div class=\"panel-body\">\n @if (Alerts.length === 0) {\n <div class=\"empty-state-small all-clear\">\n <i class=\"fa-solid fa-circle-check\"></i>\n <span>No active alerts</span>\n </div>\n }\n @for (alert of Alerts; track alert) {\n <div class=\"alert-item\" [ngClass]=\"'alert-' + alert.severity\">\n <i [class]=\"GetAlertIcon(alert.severity)\" class=\"alert-icon\"></i>\n <div class=\"alert-content\">\n <div class=\"alert-title\">{{alert.title}}</div>\n <div class=\"alert-message\">{{alert.message}}</div>\n </div>\n @if (alert.type === 'stale-lock' && alert.jobId) {\n <button\n class=\"alert-action-btn\"\n (click)=\"ReleaseLock(alert.jobId!)\">\n Release\n </button>\n }\n </div>\n }\n </div>\n </div>\n <!-- Upcoming -->\n <div class=\"panel\">\n <div class=\"panel-header\">\n <div class=\"panel-title\">\n <i class=\"fa-solid fa-calendar-day\"></i> Upcoming (24h)\n </div>\n <span class=\"panel-badge\">{{UpcomingExecutions.length}}</span>\n </div>\n <div class=\"panel-body\">\n @if (UpcomingExecutions.length === 0) {\n <div class=\"empty-state-small\">\n <i class=\"fa-solid fa-calendar-xmark\"></i>\n <span>No upcoming executions</span>\n </div>\n }\n @for (upcoming of UpcomingExecutions; track upcoming) {\n <div class=\"upcoming-item\">\n <div class=\"upcoming-info\">\n <div class=\"upcoming-name\">{{upcoming.jobName}}</div>\n <div class=\"upcoming-meta\">\n {{FormatDateTime(upcoming.nextRunAt)}}\n <span class=\"upcoming-type\">{{upcoming.jobType}}</span>\n </div>\n </div>\n <div class=\"upcoming-countdown\" [class.soon]=\"FormatTimeUntil(upcoming.nextRunAt).includes('m') && !FormatTimeUntil(upcoming.nextRunAt).includes('h')\">\n {{FormatTimeUntil(upcoming.nextRunAt)}}\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n <!-- Locks Section -->\n @if (Locks.length > 0) {\n <div class=\"panel\" style=\"margin-top: 20px;\">\n <div class=\"panel-header\">\n <div class=\"panel-title\">\n <i class=\"fa-solid fa-lock\"></i> Active Locks\n </div>\n <span class=\"panel-badge\">{{Locks.length}}</span>\n </div>\n <div class=\"panel-body\">\n <table class=\"data-table\">\n <thead>\n <tr>\n <th>Job</th>\n <th>Locked By</th>\n <th>Locked At</th>\n <th>Expected Completion</th>\n <th>Status</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (lock of Locks; track lock) {\n <tr [class.stale-row]=\"lock.isStale\">\n <td class=\"cell-name\">{{lock.jobName}}</td>\n <td class=\"cell-meta\">{{lock.lockedBy}}</td>\n <td class=\"cell-meta\">{{FormatTimeAgo(lock.lockedAt)}}</td>\n <td class=\"cell-meta\">{{FormatDateTime(lock.expectedCompletion)}}</td>\n <td>\n <span class=\"lock-status-badge\" [class.stale]=\"lock.isStale\">\n {{lock.isStale ? 'Stale' : 'Active'}}\n </span>\n </td>\n <td>\n @if (lock.isStale) {\n <button class=\"release-btn\" (click)=\"ReleaseLock(lock.jobId)\">\n <i class=\"fa-solid fa-lock-open\"></i> Release\n </button>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n </div>\n }\n</div>\n"]}
@@ -11,13 +11,12 @@ import { Subject } from 'rxjs';
11
11
  import { debounceTime, takeUntil } from 'rxjs/operators';
12
12
  import * as i0 from "@angular/core";
13
13
  import * as i1 from "@memberjunction/ng-testing";
14
- import * as i2 from "@progress/kendo-angular-dialog";
15
- import * as i3 from "@memberjunction/ng-versions";
16
- import * as i4 from "@memberjunction/ng-shared-generic";
17
- import * as i5 from "./components/testing-dashboard-tab.component";
18
- import * as i6 from "./components/testing-runs.component";
19
- import * as i7 from "./components/testing-analytics.component";
20
- import * as i8 from "./components/testing-review.component";
14
+ import * as i2 from "@memberjunction/ng-versions";
15
+ import * as i3 from "@memberjunction/ng-shared-generic";
16
+ import * as i4 from "./components/testing-dashboard-tab.component";
17
+ import * as i5 from "./components/testing-runs.component";
18
+ import * as i6 from "./components/testing-analytics.component";
19
+ import * as i7 from "./components/testing-review.component";
21
20
  function TestingDashboardComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
22
21
  i0.ɵɵelementStart(0, "div", 1);
23
22
  i0.ɵɵelement(1, "mj-loading", 3);
@@ -296,7 +295,7 @@ let TestingDashboardComponent = class TestingDashboardComponent extends BaseDash
296
295
  });
297
296
  }
298
297
  static ɵfac = function TestingDashboardComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TestingDashboardComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i1.TestingDialogService), i0.ɵɵdirectiveInject(i1.TestingExecutionService)); };
299
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestingDashboardComponent, selectors: [["mj-testing-dashboard"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 4, vars: 3, consts: [["kendoDialogContainer", "", 1, "testing-dashboard-container"], [1, "loading-container"], ["Mode", "slide", 3, "Title", "Resizable"], ["text", "Loading Testing Dashboard...", "size", "large"], [1, "active-runs-banner"], [1, "dashboard-content-wrapper"], [1, "testing-dashboard-nav"], [1, "nav-item", 3, "active"], [1, "nav-spacer"], [1, "dashboard-content"], [3, "initialState"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "active-runs-count"], [1, "active-runs-detail"], [1, "active-runs-view-btn", 3, "click"], [1, "nav-item", 3, "click"], [3, "stateChange", "initialState"], ["Mode", "slide", 3, "Closed", "Title", "Resizable"], [3, "PanelClose", "PanelMode", "selectedTestId", "selectedSuiteId", "runMode"]], template: function TestingDashboardComponent_Template(rf, ctx) { if (rf & 1) {
298
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestingDashboardComponent, selectors: [["mj-testing-dashboard"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 4, vars: 3, consts: [[1, "testing-dashboard-container"], [1, "loading-container"], ["Mode", "slide", 3, "Title", "Resizable"], ["text", "Loading Testing Dashboard...", "size", "large"], [1, "active-runs-banner"], [1, "dashboard-content-wrapper"], [1, "testing-dashboard-nav"], [1, "nav-item", 3, "active"], [1, "nav-spacer"], [1, "dashboard-content"], [3, "initialState"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "active-runs-count"], [1, "active-runs-detail"], [1, "active-runs-view-btn", 3, "click"], [1, "nav-item", 3, "click"], [3, "stateChange", "initialState"], ["Mode", "slide", 3, "Closed", "Title", "Resizable"], [3, "PanelClose", "PanelMode", "selectedTestId", "selectedSuiteId", "runMode"]], template: function TestingDashboardComponent_Template(rf, ctx) { if (rf & 1) {
300
299
  i0.ɵɵelementStart(0, "div", 0);
301
300
  i0.ɵɵconditionalCreate(1, TestingDashboardComponent_Conditional_1_Template, 2, 0, "div", 1);
302
301
  i0.ɵɵconditionalCreate(2, TestingDashboardComponent_Conditional_2_Template, 12, 5);
@@ -309,7 +308,7 @@ let TestingDashboardComponent = class TestingDashboardComponent extends BaseDash
309
308
  i0.ɵɵconditional(!ctx.isLoading ? 2 : -1);
310
309
  i0.ɵɵadvance();
311
310
  i0.ɵɵconditional(ctx.testingDialogService.IsPanelOpen ? 3 : -1);
312
- } }, dependencies: [i2.DialogContainerDirective, i1.TestRunDialogComponent, i1.EvaluationModeToggleComponent, i3.MjSlidePanelComponent, i4.LoadingComponent, i5.TestingDashboardTabComponent, i6.TestingRunsComponent, i7.TestingAnalyticsComponent, i8.TestingReviewComponent], styles: ["\n\n\n\n\n\n.testing-dashboard-container[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-bg-surface-sunken);\n position: relative;\n overflow: hidden;\n}\n\n\n\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: var(--mj-bg-surface);\n backdrop-filter: blur(8px);\n}\n\n.loading-content[_ngcontent-%COMP%] {\n text-align: center;\n}\n\n.loading-spinner[_ngcontent-%COMP%] {\n display: inline-block;\n position: relative;\n width: 80px;\n height: 80px;\n}\n\n.spinner-ring[_ngcontent-%COMP%] {\n box-sizing: border-box;\n display: block;\n position: absolute;\n width: 64px;\n height: 64px;\n margin: 8px;\n border: 8px solid var(--mj-brand-primary);\n border-radius: 50%;\n animation: _ngcontent-%COMP%_spinner-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n border-color: var(--mj-brand-primary) transparent transparent transparent;\n}\n\n.spinner-ring[_ngcontent-%COMP%]:nth-child(1) {\n animation-delay: -0.45s;\n}\n\n.spinner-ring[_ngcontent-%COMP%]:nth-child(2) {\n animation-delay: -0.3s;\n}\n\n.spinner-ring[_ngcontent-%COMP%]:nth-child(3) {\n animation-delay: -0.15s;\n}\n\n@keyframes _ngcontent-%COMP%_spinner-ring {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.loading-text[_ngcontent-%COMP%] {\n margin-top: 20px;\n font-size: 16px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n\n\n\n.dashboard-content-wrapper[_ngcontent-%COMP%] {\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.dashboard-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n}\n\n\n\n.testing-dashboard-nav[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-start;\n gap: 2px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n padding: 0 16px;\n position: relative;\n z-index: 10;\n}\n\n.nav-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 16px 24px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-bottom: 3px solid transparent;\n white-space: nowrap;\n position: relative;\n font-weight: 500;\n}\n\n.nav-item[_ngcontent-%COMP%]::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 3px;\n background: transparent;\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 8px 8px 0 0;\n}\n\n.nav-item[_ngcontent-%COMP%]:hover {\n color: var(--mj-brand-primary);\n}\n\n.nav-item[_ngcontent-%COMP%]:hover::before {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.nav-item.active[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.nav-item.active[_ngcontent-%COMP%]::before {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n position: relative;\n z-index: 1;\n}\n\n.nav-item[_ngcontent-%COMP%]:hover i[_ngcontent-%COMP%] {\n transform: scale(1.1);\n}\n\n.nav-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.2px;\n position: relative;\n z-index: 1;\n}\n\n\n\n.nav-spacer[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n\n\n.active-runs-banner[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 20px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-bottom: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-border-default));\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n.active-runs-banner[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.active-runs-count[_ngcontent-%COMP%] {\n font-weight: 600;\n}\n\n.active-runs-detail[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.active-runs-view-btn[_ngcontent-%COMP%] {\n padding: 4px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.2s;\n}\n\n.active-runs-view-btn[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n}\n\n\n\n@media (max-width: 768px) {\n .testing-dashboard-nav[_ngcontent-%COMP%] {\n padding: 0 8px;\n overflow-x: auto;\n }\n\n .nav-item[_ngcontent-%COMP%] {\n padding: 12px 16px;\n gap: 8px;\n }\n\n .nav-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 11px;\n }\n\n .nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 18px;\n }\n\n .active-runs-banner[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n padding: 8px 12px;\n gap: 6px;\n }\n\n .active-runs-detail[_ngcontent-%COMP%] {\n width: 100%;\n order: 3;\n }\n}\n\n@media (max-width: 480px) {\n .nav-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n display: none;\n }\n\n .nav-item[_ngcontent-%COMP%] {\n padding: 12px 14px;\n }\n}"], changeDetection: 0 });
311
+ } }, dependencies: [i1.TestRunDialogComponent, i1.EvaluationModeToggleComponent, i2.MjSlidePanelComponent, i3.LoadingComponent, i4.TestingDashboardTabComponent, i5.TestingRunsComponent, i6.TestingAnalyticsComponent, i7.TestingReviewComponent], styles: ["\n\n\n\n\n\n.testing-dashboard-container[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-bg-surface-sunken);\n position: relative;\n overflow: hidden;\n}\n\n\n\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: var(--mj-bg-surface);\n backdrop-filter: blur(8px);\n}\n\n.loading-content[_ngcontent-%COMP%] {\n text-align: center;\n}\n\n.loading-spinner[_ngcontent-%COMP%] {\n display: inline-block;\n position: relative;\n width: 80px;\n height: 80px;\n}\n\n.spinner-ring[_ngcontent-%COMP%] {\n box-sizing: border-box;\n display: block;\n position: absolute;\n width: 64px;\n height: 64px;\n margin: 8px;\n border: 8px solid var(--mj-brand-primary);\n border-radius: 50%;\n animation: _ngcontent-%COMP%_spinner-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n border-color: var(--mj-brand-primary) transparent transparent transparent;\n}\n\n.spinner-ring[_ngcontent-%COMP%]:nth-child(1) {\n animation-delay: -0.45s;\n}\n\n.spinner-ring[_ngcontent-%COMP%]:nth-child(2) {\n animation-delay: -0.3s;\n}\n\n.spinner-ring[_ngcontent-%COMP%]:nth-child(3) {\n animation-delay: -0.15s;\n}\n\n@keyframes _ngcontent-%COMP%_spinner-ring {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.loading-text[_ngcontent-%COMP%] {\n margin-top: 20px;\n font-size: 16px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n\n\n\n.dashboard-content-wrapper[_ngcontent-%COMP%] {\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.dashboard-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n}\n\n\n\n.testing-dashboard-nav[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-start;\n gap: 2px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n padding: 0 16px;\n position: relative;\n z-index: 10;\n}\n\n.nav-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 16px 24px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-bottom: 3px solid transparent;\n white-space: nowrap;\n position: relative;\n font-weight: 500;\n}\n\n.nav-item[_ngcontent-%COMP%]::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 3px;\n background: transparent;\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 8px 8px 0 0;\n}\n\n.nav-item[_ngcontent-%COMP%]:hover {\n color: var(--mj-brand-primary);\n}\n\n.nav-item[_ngcontent-%COMP%]:hover::before {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.nav-item.active[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.nav-item.active[_ngcontent-%COMP%]::before {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n position: relative;\n z-index: 1;\n}\n\n.nav-item[_ngcontent-%COMP%]:hover i[_ngcontent-%COMP%] {\n transform: scale(1.1);\n}\n\n.nav-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.2px;\n position: relative;\n z-index: 1;\n}\n\n\n\n.nav-spacer[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n\n\n.active-runs-banner[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 20px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-bottom: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-border-default));\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n.active-runs-banner[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.active-runs-count[_ngcontent-%COMP%] {\n font-weight: 600;\n}\n\n.active-runs-detail[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.active-runs-view-btn[_ngcontent-%COMP%] {\n padding: 4px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.2s;\n}\n\n.active-runs-view-btn[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n}\n\n\n\n@media (max-width: 768px) {\n .testing-dashboard-nav[_ngcontent-%COMP%] {\n padding: 0 8px;\n overflow-x: auto;\n }\n\n .nav-item[_ngcontent-%COMP%] {\n padding: 12px 16px;\n gap: 8px;\n }\n\n .nav-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 11px;\n }\n\n .nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 18px;\n }\n\n .active-runs-banner[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n padding: 8px 12px;\n gap: 6px;\n }\n\n .active-runs-detail[_ngcontent-%COMP%] {\n width: 100%;\n order: 3;\n }\n}\n\n@media (max-width: 480px) {\n .nav-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n display: none;\n }\n\n .nav-item[_ngcontent-%COMP%] {\n padding: 12px 14px;\n }\n}"], changeDetection: 0 });
313
312
  };
314
313
  TestingDashboardComponent = __decorate([
315
314
  RegisterClass(BaseDashboard, 'TestingDashboard')
@@ -317,7 +316,7 @@ TestingDashboardComponent = __decorate([
317
316
  export { TestingDashboardComponent };
318
317
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TestingDashboardComponent, [{
319
318
  type: Component,
320
- args: [{ standalone: false, selector: 'mj-testing-dashboard', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"testing-dashboard-container\" kendoDialogContainer>\n @if (isLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading Testing Dashboard...\" size=\"large\"></mj-loading>\n </div>\n }\n\n @if (!isLoading) {\n <!-- Active Runs Banner -->\n @if (ActiveRuns.length > 0) {\n <div class=\"active-runs-banner\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span class=\"active-runs-count\">{{ ActiveRuns.length }} test{{ ActiveRuns.length === 1 ? '' : 's' }} running</span>\n <span class=\"active-runs-detail\">{{ ActiveRuns[0].TestName }} &mdash; {{ ActiveRuns[0].Progress }}%</span>\n <button class=\"active-runs-view-btn\" (click)=\"OnViewActiveRun(ActiveRuns[0])\">View</button>\n </div>\n }\n\n <div class=\"dashboard-content-wrapper\">\n <div class=\"testing-dashboard-nav\">\n @for (navItem of navigationConfig; track $index) {\n <div\n class=\"nav-item\"\n [class.active]=\"navItem.selected\"\n (click)=\"onTabChange(navigationItems[$index])\"\n >\n <i [class]=\"navItem.icon\"></i>\n <span>{{ navItem.text }}</span>\n </div>\n }\n <div class=\"nav-spacer\"></div>\n <app-evaluation-mode-toggle></app-evaluation-mode-toggle>\n </div>\n\n <div class=\"dashboard-content\">\n @if (activeTab === 'dashboard' && hasVisited('dashboard')) {\n <app-testing-dashboard-tab\n [initialState]=\"dashboardState\"\n (stateChange)=\"onDashboardStateChange($event)\"\n ></app-testing-dashboard-tab>\n }\n\n @if (activeTab === 'runs' && hasVisited('runs')) {\n <app-testing-runs\n [initialState]=\"runsState\"\n (stateChange)=\"onRunsStateChange($event)\"\n ></app-testing-runs>\n }\n\n @if (activeTab === 'analytics' && hasVisited('analytics')) {\n <app-testing-analytics\n [initialState]=\"analyticsState\"\n (stateChange)=\"onAnalyticsStateChange($event)\"\n ></app-testing-analytics>\n }\n\n @if (activeTab === 'review' && hasVisited('review')) {\n <app-testing-review\n [initialState]=\"reviewState\"\n (stateChange)=\"onReviewStateChange($event)\"\n ></app-testing-review>\n }\n </div>\n </div>\n }\n\n <!-- Slide Panel for Test Execution -->\n @if (testingDialogService.IsPanelOpen) {\n <mj-slide-panel\n Mode=\"slide\"\n [Title]=\"testingDialogService.PanelOptions?.testId ? 'Test Execution' : 'Run Test'\"\n [Resizable]=\"true\"\n (Closed)=\"OnPanelClosed()\">\n <app-test-run-dialog\n [PanelMode]=\"true\"\n [selectedTestId]=\"testingDialogService.PanelOptions?.testId ?? null\"\n [selectedSuiteId]=\"testingDialogService.PanelOptions?.suiteId ?? null\"\n [runMode]=\"testingDialogService.PanelOptions?.mode ?? 'test'\"\n (PanelClose)=\"OnPanelClosed()\">\n </app-test-run-dialog>\n </mj-slide-panel>\n }\n</div>\n", styles: ["/* ============================================\n Testing Dashboard - Premium Design System\n Based on AI Dashboard patterns\n ============================================ */\n\n.testing-dashboard-container {\n height: 100%;\n background: var(--mj-bg-surface-sunken);\n position: relative;\n overflow: hidden;\n}\n\n/* Loading State */\n.loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: var(--mj-bg-surface);\n backdrop-filter: blur(8px);\n}\n\n.loading-content {\n text-align: center;\n}\n\n.loading-spinner {\n display: inline-block;\n position: relative;\n width: 80px;\n height: 80px;\n}\n\n.spinner-ring {\n box-sizing: border-box;\n display: block;\n position: absolute;\n width: 64px;\n height: 64px;\n margin: 8px;\n border: 8px solid var(--mj-brand-primary);\n border-radius: 50%;\n animation: spinner-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n border-color: var(--mj-brand-primary) transparent transparent transparent;\n}\n\n.spinner-ring:nth-child(1) {\n animation-delay: -0.45s;\n}\n\n.spinner-ring:nth-child(2) {\n animation-delay: -0.3s;\n}\n\n.spinner-ring:nth-child(3) {\n animation-delay: -0.15s;\n}\n\n@keyframes spinner-ring {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.loading-text {\n margin-top: 20px;\n font-size: 16px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n\n/* Dashboard Layout */\n.dashboard-content-wrapper {\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.dashboard-content {\n flex: 1;\n overflow: hidden;\n}\n\n/* Premium Navigation Tabs */\n.testing-dashboard-nav {\n display: flex;\n justify-content: flex-start;\n gap: 2px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n padding: 0 16px;\n position: relative;\n z-index: 10;\n}\n\n.nav-item {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 16px 24px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-bottom: 3px solid transparent;\n white-space: nowrap;\n position: relative;\n font-weight: 500;\n}\n\n.nav-item::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 3px;\n background: transparent;\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 8px 8px 0 0;\n}\n\n.nav-item:hover {\n color: var(--mj-brand-primary);\n}\n\n.nav-item:hover::before {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.nav-item.active {\n color: var(--mj-brand-primary);\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.nav-item.active::before {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.nav-item i {\n font-size: 16px;\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n position: relative;\n z-index: 1;\n}\n\n.nav-item:hover i {\n transform: scale(1.1);\n}\n\n.nav-item span {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.2px;\n position: relative;\n z-index: 1;\n}\n\n/* Spacer for flex layout */\n.nav-spacer {\n flex: 1;\n}\n\n/* Active Runs Banner */\n.active-runs-banner {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 20px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-bottom: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-border-default));\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n.active-runs-banner i {\n color: var(--mj-brand-primary);\n}\n\n.active-runs-count {\n font-weight: 600;\n}\n\n.active-runs-detail {\n color: var(--mj-text-secondary);\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.active-runs-view-btn {\n padding: 4px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.2s;\n}\n\n.active-runs-view-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n}\n\n/* Responsive */\n@media (max-width: 768px) {\n .testing-dashboard-nav {\n padding: 0 8px;\n overflow-x: auto;\n }\n\n .nav-item {\n padding: 12px 16px;\n gap: 8px;\n }\n\n .nav-item span {\n font-size: 11px;\n }\n\n .nav-item i {\n font-size: 18px;\n }\n\n .active-runs-banner {\n flex-wrap: wrap;\n padding: 8px 12px;\n gap: 6px;\n }\n\n .active-runs-detail {\n width: 100%;\n order: 3;\n }\n}\n\n@media (max-width: 480px) {\n .nav-item span {\n display: none;\n }\n\n .nav-item {\n padding: 12px 14px;\n }\n}\n"] }]
319
+ args: [{ standalone: false, selector: 'mj-testing-dashboard', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"testing-dashboard-container\">\n @if (isLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading Testing Dashboard...\" size=\"large\"></mj-loading>\n </div>\n }\n\n @if (!isLoading) {\n <!-- Active Runs Banner -->\n @if (ActiveRuns.length > 0) {\n <div class=\"active-runs-banner\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span class=\"active-runs-count\">{{ ActiveRuns.length }} test{{ ActiveRuns.length === 1 ? '' : 's' }} running</span>\n <span class=\"active-runs-detail\">{{ ActiveRuns[0].TestName }} &mdash; {{ ActiveRuns[0].Progress }}%</span>\n <button class=\"active-runs-view-btn\" (click)=\"OnViewActiveRun(ActiveRuns[0])\">View</button>\n </div>\n }\n\n <div class=\"dashboard-content-wrapper\">\n <div class=\"testing-dashboard-nav\">\n @for (navItem of navigationConfig; track $index) {\n <div\n class=\"nav-item\"\n [class.active]=\"navItem.selected\"\n (click)=\"onTabChange(navigationItems[$index])\"\n >\n <i [class]=\"navItem.icon\"></i>\n <span>{{ navItem.text }}</span>\n </div>\n }\n <div class=\"nav-spacer\"></div>\n <app-evaluation-mode-toggle></app-evaluation-mode-toggle>\n </div>\n\n <div class=\"dashboard-content\">\n @if (activeTab === 'dashboard' && hasVisited('dashboard')) {\n <app-testing-dashboard-tab\n [initialState]=\"dashboardState\"\n (stateChange)=\"onDashboardStateChange($event)\"\n ></app-testing-dashboard-tab>\n }\n\n @if (activeTab === 'runs' && hasVisited('runs')) {\n <app-testing-runs\n [initialState]=\"runsState\"\n (stateChange)=\"onRunsStateChange($event)\"\n ></app-testing-runs>\n }\n\n @if (activeTab === 'analytics' && hasVisited('analytics')) {\n <app-testing-analytics\n [initialState]=\"analyticsState\"\n (stateChange)=\"onAnalyticsStateChange($event)\"\n ></app-testing-analytics>\n }\n\n @if (activeTab === 'review' && hasVisited('review')) {\n <app-testing-review\n [initialState]=\"reviewState\"\n (stateChange)=\"onReviewStateChange($event)\"\n ></app-testing-review>\n }\n </div>\n </div>\n }\n\n <!-- Slide Panel for Test Execution -->\n @if (testingDialogService.IsPanelOpen) {\n <mj-slide-panel\n Mode=\"slide\"\n [Title]=\"testingDialogService.PanelOptions?.testId ? 'Test Execution' : 'Run Test'\"\n [Resizable]=\"true\"\n (Closed)=\"OnPanelClosed()\">\n <app-test-run-dialog\n [PanelMode]=\"true\"\n [selectedTestId]=\"testingDialogService.PanelOptions?.testId ?? null\"\n [selectedSuiteId]=\"testingDialogService.PanelOptions?.suiteId ?? null\"\n [runMode]=\"testingDialogService.PanelOptions?.mode ?? 'test'\"\n (PanelClose)=\"OnPanelClosed()\">\n </app-test-run-dialog>\n </mj-slide-panel>\n }\n</div>\n", styles: ["/* ============================================\n Testing Dashboard - Premium Design System\n Based on AI Dashboard patterns\n ============================================ */\n\n.testing-dashboard-container {\n height: 100%;\n background: var(--mj-bg-surface-sunken);\n position: relative;\n overflow: hidden;\n}\n\n/* Loading State */\n.loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: var(--mj-bg-surface);\n backdrop-filter: blur(8px);\n}\n\n.loading-content {\n text-align: center;\n}\n\n.loading-spinner {\n display: inline-block;\n position: relative;\n width: 80px;\n height: 80px;\n}\n\n.spinner-ring {\n box-sizing: border-box;\n display: block;\n position: absolute;\n width: 64px;\n height: 64px;\n margin: 8px;\n border: 8px solid var(--mj-brand-primary);\n border-radius: 50%;\n animation: spinner-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n border-color: var(--mj-brand-primary) transparent transparent transparent;\n}\n\n.spinner-ring:nth-child(1) {\n animation-delay: -0.45s;\n}\n\n.spinner-ring:nth-child(2) {\n animation-delay: -0.3s;\n}\n\n.spinner-ring:nth-child(3) {\n animation-delay: -0.15s;\n}\n\n@keyframes spinner-ring {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.loading-text {\n margin-top: 20px;\n font-size: 16px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n\n/* Dashboard Layout */\n.dashboard-content-wrapper {\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.dashboard-content {\n flex: 1;\n overflow: hidden;\n}\n\n/* Premium Navigation Tabs */\n.testing-dashboard-nav {\n display: flex;\n justify-content: flex-start;\n gap: 2px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n box-shadow: var(--mj-shadow-sm);\n padding: 0 16px;\n position: relative;\n z-index: 10;\n}\n\n.nav-item {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 16px 24px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-bottom: 3px solid transparent;\n white-space: nowrap;\n position: relative;\n font-weight: 500;\n}\n\n.nav-item::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 3px;\n background: transparent;\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 8px 8px 0 0;\n}\n\n.nav-item:hover {\n color: var(--mj-brand-primary);\n}\n\n.nav-item:hover::before {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.nav-item.active {\n color: var(--mj-brand-primary);\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.nav-item.active::before {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.nav-item i {\n font-size: 16px;\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n position: relative;\n z-index: 1;\n}\n\n.nav-item:hover i {\n transform: scale(1.1);\n}\n\n.nav-item span {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.2px;\n position: relative;\n z-index: 1;\n}\n\n/* Spacer for flex layout */\n.nav-spacer {\n flex: 1;\n}\n\n/* Active Runs Banner */\n.active-runs-banner {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 20px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-bottom: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-border-default));\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n.active-runs-banner i {\n color: var(--mj-brand-primary);\n}\n\n.active-runs-count {\n font-weight: 600;\n}\n\n.active-runs-detail {\n color: var(--mj-text-secondary);\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.active-runs-view-btn {\n padding: 4px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.2s;\n}\n\n.active-runs-view-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n}\n\n/* Responsive */\n@media (max-width: 768px) {\n .testing-dashboard-nav {\n padding: 0 8px;\n overflow-x: auto;\n }\n\n .nav-item {\n padding: 12px 16px;\n gap: 8px;\n }\n\n .nav-item span {\n font-size: 11px;\n }\n\n .nav-item i {\n font-size: 18px;\n }\n\n .active-runs-banner {\n flex-wrap: wrap;\n padding: 8px 12px;\n gap: 6px;\n }\n\n .active-runs-detail {\n width: 100%;\n order: 3;\n }\n}\n\n@media (max-width: 480px) {\n .nav-item span {\n display: none;\n }\n\n .nav-item {\n padding: 12px 14px;\n }\n}\n"] }]
321
320
  }], () => [{ type: i0.ChangeDetectorRef }, { type: i1.TestingDialogService }, { type: i1.TestingExecutionService }], null); })();
322
321
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TestingDashboardComponent, { className: "TestingDashboardComponent", filePath: "src/Testing/testing-dashboard.component.ts", lineNumber: 25 }); })();
323
322
  //# sourceMappingURL=testing-dashboard.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing-dashboard.component.js","sourceRoot":"","sources":["../../src/Testing/testing-dashboard.component.ts","../../src/Testing/testing-dashboard.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, AfterViewInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';\nimport { BaseDashboard } from '@memberjunction/ng-shared';\nimport { RegisterClass } from '@memberjunction/global';\nimport { Subject } from 'rxjs';\nimport { debounceTime, takeUntil } from 'rxjs/operators';\nimport { ResourceData } from '@memberjunction/core-entities';\nimport { TestingDialogService, TestingExecutionService, ActiveRun } from '@memberjunction/ng-testing';\n\ninterface TestingDashboardState {\n activeTab: string;\n dashboardState: Record<string, unknown>;\n runsState: Record<string, unknown>;\n analyticsState: Record<string, unknown>;\n reviewState: Record<string, unknown>;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-testing-dashboard',\n templateUrl: './testing-dashboard.component.html',\n styleUrls: ['./testing-dashboard.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\n@RegisterClass(BaseDashboard, 'TestingDashboard')\nexport class TestingDashboardComponent extends BaseDashboard implements AfterViewInit, OnDestroy {\n\n public isLoading = false;\n public activeTab = 'dashboard';\n public selectedIndex = 0;\n\n // Active test runs from execution service\n public ActiveRuns: ActiveRun[] = [];\n\n // Component states\n public dashboardState: Record<string, unknown> | null = null;\n public runsState: Record<string, unknown> | null = null;\n public analyticsState: Record<string, unknown> | null = null;\n public reviewState: Record<string, unknown> | null = null;\n\n // Track visited tabs for lazy loading\n private visitedTabs = new Set<string>();\n\n // Navigation items\n public navigationItems: string[] = ['dashboard', 'runs', 'analytics', 'review'];\n\n public navigationConfig = [\n { text: 'Dashboard', icon: 'fa-solid fa-gauge-high', selected: false },\n { text: 'Runs', icon: 'fa-solid fa-play-circle', selected: false },\n { text: 'Analytics', icon: 'fa-solid fa-chart-bar', selected: false },\n { text: 'Review', icon: 'fa-solid fa-clipboard-check', selected: false }\n ];\n\n private stateChangeSubject = new Subject<TestingDashboardState>();\n private destroy$ = new Subject<void>();\n\n constructor(\n private cdr: ChangeDetectorRef,\n public testingDialogService: TestingDialogService,\n private executionService: TestingExecutionService\n ) {\n super();\n this.setupStateManagement();\n this.updateNavigationSelection();\n }\n\n async GetResourceDisplayName(data: ResourceData): Promise<string> {\n return 'Testing';\n }\n\n ngAfterViewInit(): void {\n this.visitedTabs.add(this.activeTab);\n this.updateNavigationSelection();\n this.emitStateChange();\n\n this.executionService.ActiveRuns$.pipe(\n takeUntil(this.destroy$)\n ).subscribe(runs => {\n this.ActiveRuns = runs;\n this.cdr.markForCheck();\n });\n\n this.testingDialogService.PanelStateChanged$.pipe(\n takeUntil(this.destroy$)\n ).subscribe((isOpen) => {\n console.log('[TestingDashboard] PanelStateChanged$:', isOpen, 'IsPanelOpen:', this.testingDialogService.IsPanelOpen);\n this.cdr.detectChanges();\n });\n\n this.cdr.detectChanges();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n this.stateChangeSubject.complete();\n }\n\n public onTabChange(tabId: string): void {\n this.activeTab = tabId;\n const index = this.navigationItems.indexOf(tabId);\n this.selectedIndex = index >= 0 ? index : 0;\n this.updateNavigationSelection();\n this.visitedTabs.add(tabId);\n this.emitStateChange();\n this.cdr.markForCheck();\n }\n\n public hasVisited(tabId: string): boolean {\n return this.visitedTabs.has(tabId);\n }\n\n private setupStateManagement(): void {\n this.stateChangeSubject.pipe(\n debounceTime(50)\n ).subscribe(state => {\n this.UserStateChanged.emit(state);\n });\n }\n\n private emitStateChange(): void {\n const state: TestingDashboardState = {\n activeTab: this.activeTab,\n dashboardState: (this.dashboardState || {}) as Record<string, unknown>,\n runsState: (this.runsState || {}) as Record<string, unknown>,\n analyticsState: (this.analyticsState || {}) as Record<string, unknown>,\n reviewState: (this.reviewState || {}) as Record<string, unknown>\n };\n\n this.stateChangeSubject.next(state);\n }\n\n public onDashboardStateChange(state: Record<string, unknown>): void {\n this.dashboardState = state;\n this.emitStateChange();\n }\n\n public onRunsStateChange(state: Record<string, unknown>): void {\n this.runsState = state;\n this.emitStateChange();\n }\n\n public onAnalyticsStateChange(state: Record<string, unknown>): void {\n this.analyticsState = state;\n this.emitStateChange();\n }\n\n public onReviewStateChange(state: Record<string, unknown>): void {\n this.reviewState = state;\n this.emitStateChange();\n }\n\n public loadUserState(state: Partial<TestingDashboardState>): void {\n if (state.activeTab) {\n this.activeTab = state.activeTab;\n const index = this.navigationItems.indexOf(state.activeTab);\n this.selectedIndex = index >= 0 ? index : 0;\n this.visitedTabs.add(state.activeTab);\n this.updateNavigationSelection();\n }\n\n if (state.dashboardState) this.dashboardState = state.dashboardState;\n if (state.runsState) this.runsState = state.runsState;\n if (state.analyticsState) this.analyticsState = state.analyticsState;\n if (state.reviewState) this.reviewState = state.reviewState;\n\n this.cdr.markForCheck();\n }\n\n initDashboard(): void {\n try {\n this.isLoading = true;\n } catch (error) {\n console.error('Error initializing Testing dashboard:', error);\n this.Error.emit(new Error('Failed to initialize Testing dashboard. Please try again.'));\n } finally {\n this.isLoading = false;\n }\n }\n\n loadData(): void {\n if (this.Config?.userState) {\n setTimeout(() => {\n if (this.Config?.userState) {\n this.loadUserState(this.Config.userState);\n }\n }, 0);\n }\n\n this.NotifyLoadComplete();\n }\n\n public getCurrentTabLabel(): string {\n const tabIndex = this.navigationItems.indexOf(this.activeTab);\n return tabIndex >= 0 ? this.navigationConfig[tabIndex].text : 'Testing Dashboard';\n }\n\n public OnPanelClosed(): void {\n this.testingDialogService.ClosePanel();\n this.cdr.markForCheck();\n }\n\n public OnViewActiveRun(run: ActiveRun): void {\n this.testingDialogService.OpenTestPanel(run.TestId);\n this.cdr.markForCheck();\n }\n\n public OnViewRunningTestFromTab(testId: string): void {\n this.testingDialogService.OpenTestPanel(testId);\n this.cdr.detectChanges();\n }\n\n private updateNavigationSelection(): void {\n this.navigationConfig.forEach((item, index) => {\n item.selected = this.navigationItems[index] === this.activeTab;\n });\n }\n}\n","<div class=\"testing-dashboard-container\" kendoDialogContainer>\n @if (isLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading Testing Dashboard...\" size=\"large\"></mj-loading>\n </div>\n }\n\n @if (!isLoading) {\n <!-- Active Runs Banner -->\n @if (ActiveRuns.length > 0) {\n <div class=\"active-runs-banner\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span class=\"active-runs-count\">{{ ActiveRuns.length }} test{{ ActiveRuns.length === 1 ? '' : 's' }} running</span>\n <span class=\"active-runs-detail\">{{ ActiveRuns[0].TestName }} &mdash; {{ ActiveRuns[0].Progress }}%</span>\n <button class=\"active-runs-view-btn\" (click)=\"OnViewActiveRun(ActiveRuns[0])\">View</button>\n </div>\n }\n\n <div class=\"dashboard-content-wrapper\">\n <div class=\"testing-dashboard-nav\">\n @for (navItem of navigationConfig; track $index) {\n <div\n class=\"nav-item\"\n [class.active]=\"navItem.selected\"\n (click)=\"onTabChange(navigationItems[$index])\"\n >\n <i [class]=\"navItem.icon\"></i>\n <span>{{ navItem.text }}</span>\n </div>\n }\n <div class=\"nav-spacer\"></div>\n <app-evaluation-mode-toggle></app-evaluation-mode-toggle>\n </div>\n\n <div class=\"dashboard-content\">\n @if (activeTab === 'dashboard' && hasVisited('dashboard')) {\n <app-testing-dashboard-tab\n [initialState]=\"dashboardState\"\n (stateChange)=\"onDashboardStateChange($event)\"\n ></app-testing-dashboard-tab>\n }\n\n @if (activeTab === 'runs' && hasVisited('runs')) {\n <app-testing-runs\n [initialState]=\"runsState\"\n (stateChange)=\"onRunsStateChange($event)\"\n ></app-testing-runs>\n }\n\n @if (activeTab === 'analytics' && hasVisited('analytics')) {\n <app-testing-analytics\n [initialState]=\"analyticsState\"\n (stateChange)=\"onAnalyticsStateChange($event)\"\n ></app-testing-analytics>\n }\n\n @if (activeTab === 'review' && hasVisited('review')) {\n <app-testing-review\n [initialState]=\"reviewState\"\n (stateChange)=\"onReviewStateChange($event)\"\n ></app-testing-review>\n }\n </div>\n </div>\n }\n\n <!-- Slide Panel for Test Execution -->\n @if (testingDialogService.IsPanelOpen) {\n <mj-slide-panel\n Mode=\"slide\"\n [Title]=\"testingDialogService.PanelOptions?.testId ? 'Test Execution' : 'Run Test'\"\n [Resizable]=\"true\"\n (Closed)=\"OnPanelClosed()\">\n <app-test-run-dialog\n [PanelMode]=\"true\"\n [selectedTestId]=\"testingDialogService.PanelOptions?.testId ?? null\"\n [selectedSuiteId]=\"testingDialogService.PanelOptions?.suiteId ?? null\"\n [runMode]=\"testingDialogService.PanelOptions?.mode ?? 'test'\"\n (PanelClose)=\"OnPanelClosed()\">\n </app-test-run-dialog>\n </mj-slide-panel>\n }\n</div>\n"]}
1
+ {"version":3,"file":"testing-dashboard.component.js","sourceRoot":"","sources":["../../src/Testing/testing-dashboard.component.ts","../../src/Testing/testing-dashboard.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, AfterViewInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';\nimport { BaseDashboard } from '@memberjunction/ng-shared';\nimport { RegisterClass } from '@memberjunction/global';\nimport { Subject } from 'rxjs';\nimport { debounceTime, takeUntil } from 'rxjs/operators';\nimport { ResourceData } from '@memberjunction/core-entities';\nimport { TestingDialogService, TestingExecutionService, ActiveRun } from '@memberjunction/ng-testing';\n\ninterface TestingDashboardState {\n activeTab: string;\n dashboardState: Record<string, unknown>;\n runsState: Record<string, unknown>;\n analyticsState: Record<string, unknown>;\n reviewState: Record<string, unknown>;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-testing-dashboard',\n templateUrl: './testing-dashboard.component.html',\n styleUrls: ['./testing-dashboard.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\n@RegisterClass(BaseDashboard, 'TestingDashboard')\nexport class TestingDashboardComponent extends BaseDashboard implements AfterViewInit, OnDestroy {\n\n public isLoading = false;\n public activeTab = 'dashboard';\n public selectedIndex = 0;\n\n // Active test runs from execution service\n public ActiveRuns: ActiveRun[] = [];\n\n // Component states\n public dashboardState: Record<string, unknown> | null = null;\n public runsState: Record<string, unknown> | null = null;\n public analyticsState: Record<string, unknown> | null = null;\n public reviewState: Record<string, unknown> | null = null;\n\n // Track visited tabs for lazy loading\n private visitedTabs = new Set<string>();\n\n // Navigation items\n public navigationItems: string[] = ['dashboard', 'runs', 'analytics', 'review'];\n\n public navigationConfig = [\n { text: 'Dashboard', icon: 'fa-solid fa-gauge-high', selected: false },\n { text: 'Runs', icon: 'fa-solid fa-play-circle', selected: false },\n { text: 'Analytics', icon: 'fa-solid fa-chart-bar', selected: false },\n { text: 'Review', icon: 'fa-solid fa-clipboard-check', selected: false }\n ];\n\n private stateChangeSubject = new Subject<TestingDashboardState>();\n private destroy$ = new Subject<void>();\n\n constructor(\n private cdr: ChangeDetectorRef,\n public testingDialogService: TestingDialogService,\n private executionService: TestingExecutionService\n ) {\n super();\n this.setupStateManagement();\n this.updateNavigationSelection();\n }\n\n async GetResourceDisplayName(data: ResourceData): Promise<string> {\n return 'Testing';\n }\n\n ngAfterViewInit(): void {\n this.visitedTabs.add(this.activeTab);\n this.updateNavigationSelection();\n this.emitStateChange();\n\n this.executionService.ActiveRuns$.pipe(\n takeUntil(this.destroy$)\n ).subscribe(runs => {\n this.ActiveRuns = runs;\n this.cdr.markForCheck();\n });\n\n this.testingDialogService.PanelStateChanged$.pipe(\n takeUntil(this.destroy$)\n ).subscribe((isOpen) => {\n console.log('[TestingDashboard] PanelStateChanged$:', isOpen, 'IsPanelOpen:', this.testingDialogService.IsPanelOpen);\n this.cdr.detectChanges();\n });\n\n this.cdr.detectChanges();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n this.stateChangeSubject.complete();\n }\n\n public onTabChange(tabId: string): void {\n this.activeTab = tabId;\n const index = this.navigationItems.indexOf(tabId);\n this.selectedIndex = index >= 0 ? index : 0;\n this.updateNavigationSelection();\n this.visitedTabs.add(tabId);\n this.emitStateChange();\n this.cdr.markForCheck();\n }\n\n public hasVisited(tabId: string): boolean {\n return this.visitedTabs.has(tabId);\n }\n\n private setupStateManagement(): void {\n this.stateChangeSubject.pipe(\n debounceTime(50)\n ).subscribe(state => {\n this.UserStateChanged.emit(state);\n });\n }\n\n private emitStateChange(): void {\n const state: TestingDashboardState = {\n activeTab: this.activeTab,\n dashboardState: (this.dashboardState || {}) as Record<string, unknown>,\n runsState: (this.runsState || {}) as Record<string, unknown>,\n analyticsState: (this.analyticsState || {}) as Record<string, unknown>,\n reviewState: (this.reviewState || {}) as Record<string, unknown>\n };\n\n this.stateChangeSubject.next(state);\n }\n\n public onDashboardStateChange(state: Record<string, unknown>): void {\n this.dashboardState = state;\n this.emitStateChange();\n }\n\n public onRunsStateChange(state: Record<string, unknown>): void {\n this.runsState = state;\n this.emitStateChange();\n }\n\n public onAnalyticsStateChange(state: Record<string, unknown>): void {\n this.analyticsState = state;\n this.emitStateChange();\n }\n\n public onReviewStateChange(state: Record<string, unknown>): void {\n this.reviewState = state;\n this.emitStateChange();\n }\n\n public loadUserState(state: Partial<TestingDashboardState>): void {\n if (state.activeTab) {\n this.activeTab = state.activeTab;\n const index = this.navigationItems.indexOf(state.activeTab);\n this.selectedIndex = index >= 0 ? index : 0;\n this.visitedTabs.add(state.activeTab);\n this.updateNavigationSelection();\n }\n\n if (state.dashboardState) this.dashboardState = state.dashboardState;\n if (state.runsState) this.runsState = state.runsState;\n if (state.analyticsState) this.analyticsState = state.analyticsState;\n if (state.reviewState) this.reviewState = state.reviewState;\n\n this.cdr.markForCheck();\n }\n\n initDashboard(): void {\n try {\n this.isLoading = true;\n } catch (error) {\n console.error('Error initializing Testing dashboard:', error);\n this.Error.emit(new Error('Failed to initialize Testing dashboard. Please try again.'));\n } finally {\n this.isLoading = false;\n }\n }\n\n loadData(): void {\n if (this.Config?.userState) {\n setTimeout(() => {\n if (this.Config?.userState) {\n this.loadUserState(this.Config.userState);\n }\n }, 0);\n }\n\n this.NotifyLoadComplete();\n }\n\n public getCurrentTabLabel(): string {\n const tabIndex = this.navigationItems.indexOf(this.activeTab);\n return tabIndex >= 0 ? this.navigationConfig[tabIndex].text : 'Testing Dashboard';\n }\n\n public OnPanelClosed(): void {\n this.testingDialogService.ClosePanel();\n this.cdr.markForCheck();\n }\n\n public OnViewActiveRun(run: ActiveRun): void {\n this.testingDialogService.OpenTestPanel(run.TestId);\n this.cdr.markForCheck();\n }\n\n public OnViewRunningTestFromTab(testId: string): void {\n this.testingDialogService.OpenTestPanel(testId);\n this.cdr.detectChanges();\n }\n\n private updateNavigationSelection(): void {\n this.navigationConfig.forEach((item, index) => {\n item.selected = this.navigationItems[index] === this.activeTab;\n });\n }\n}\n","<div class=\"testing-dashboard-container\">\n @if (isLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading Testing Dashboard...\" size=\"large\"></mj-loading>\n </div>\n }\n\n @if (!isLoading) {\n <!-- Active Runs Banner -->\n @if (ActiveRuns.length > 0) {\n <div class=\"active-runs-banner\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span class=\"active-runs-count\">{{ ActiveRuns.length }} test{{ ActiveRuns.length === 1 ? '' : 's' }} running</span>\n <span class=\"active-runs-detail\">{{ ActiveRuns[0].TestName }} &mdash; {{ ActiveRuns[0].Progress }}%</span>\n <button class=\"active-runs-view-btn\" (click)=\"OnViewActiveRun(ActiveRuns[0])\">View</button>\n </div>\n }\n\n <div class=\"dashboard-content-wrapper\">\n <div class=\"testing-dashboard-nav\">\n @for (navItem of navigationConfig; track $index) {\n <div\n class=\"nav-item\"\n [class.active]=\"navItem.selected\"\n (click)=\"onTabChange(navigationItems[$index])\"\n >\n <i [class]=\"navItem.icon\"></i>\n <span>{{ navItem.text }}</span>\n </div>\n }\n <div class=\"nav-spacer\"></div>\n <app-evaluation-mode-toggle></app-evaluation-mode-toggle>\n </div>\n\n <div class=\"dashboard-content\">\n @if (activeTab === 'dashboard' && hasVisited('dashboard')) {\n <app-testing-dashboard-tab\n [initialState]=\"dashboardState\"\n (stateChange)=\"onDashboardStateChange($event)\"\n ></app-testing-dashboard-tab>\n }\n\n @if (activeTab === 'runs' && hasVisited('runs')) {\n <app-testing-runs\n [initialState]=\"runsState\"\n (stateChange)=\"onRunsStateChange($event)\"\n ></app-testing-runs>\n }\n\n @if (activeTab === 'analytics' && hasVisited('analytics')) {\n <app-testing-analytics\n [initialState]=\"analyticsState\"\n (stateChange)=\"onAnalyticsStateChange($event)\"\n ></app-testing-analytics>\n }\n\n @if (activeTab === 'review' && hasVisited('review')) {\n <app-testing-review\n [initialState]=\"reviewState\"\n (stateChange)=\"onReviewStateChange($event)\"\n ></app-testing-review>\n }\n </div>\n </div>\n }\n\n <!-- Slide Panel for Test Execution -->\n @if (testingDialogService.IsPanelOpen) {\n <mj-slide-panel\n Mode=\"slide\"\n [Title]=\"testingDialogService.PanelOptions?.testId ? 'Test Execution' : 'Run Test'\"\n [Resizable]=\"true\"\n (Closed)=\"OnPanelClosed()\">\n <app-test-run-dialog\n [PanelMode]=\"true\"\n [selectedTestId]=\"testingDialogService.PanelOptions?.testId ?? null\"\n [selectedSuiteId]=\"testingDialogService.PanelOptions?.suiteId ?? null\"\n [runMode]=\"testingDialogService.PanelOptions?.mode ?? 'test'\"\n (PanelClose)=\"OnPanelClosed()\">\n </app-test-run-dialog>\n </mj-slide-panel>\n }\n</div>\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=analytics-resource.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics-resource.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/analytics-resource.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Tests for analytics resource component logic:
3
+ * - AN-1: Drill-down Open Record
4
+ * - AN-3: CSV Export
5
+ * - SR-6: Preference persistence
6
+ */
7
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
8
+ // ── Test the pure-logic utility functions extracted from the component ──
9
+ describe('Analytics Resource — CSV Export (AN-3)', () => {
10
+ // Test the CSV generation logic independently
11
+ function buildCSV(columns, data) {
12
+ const escape = (v) => {
13
+ const s = String(v ?? '');
14
+ return s.includes(',') || s.includes('"') || s.includes('\n')
15
+ ? `"${s.replace(/"/g, '""')}"` : s;
16
+ };
17
+ const header = columns.map(escape).join(',');
18
+ const rows = data.map(row => columns.map(c => escape(row[c])).join(','));
19
+ return [header, ...rows].join('\n');
20
+ }
21
+ it('should generate valid CSV header', () => {
22
+ const csv = buildCSV(['Name', 'Count'], []);
23
+ expect(csv).toBe('Name,Count');
24
+ });
25
+ it('should generate CSV with data rows', () => {
26
+ const csv = buildCSV(['Name', 'Score'], [
27
+ { Name: 'Tag A', Score: 42 },
28
+ { Name: 'Tag B', Score: 18 },
29
+ ]);
30
+ const lines = csv.split('\n');
31
+ expect(lines).toHaveLength(3);
32
+ expect(lines[0]).toBe('Name,Score');
33
+ expect(lines[1]).toBe('Tag A,42');
34
+ expect(lines[2]).toBe('Tag B,18');
35
+ });
36
+ it('should escape values containing commas', () => {
37
+ const csv = buildCSV(['Name'], [{ Name: 'Doe, John' }]);
38
+ expect(csv).toContain('"Doe, John"');
39
+ });
40
+ it('should escape values containing quotes', () => {
41
+ const csv = buildCSV(['Name'], [{ Name: 'He said "hello"' }]);
42
+ expect(csv).toContain('"He said ""hello"""');
43
+ });
44
+ it('should handle null values', () => {
45
+ const csv = buildCSV(['Name', 'Value'], [{ Name: 'Test', Value: null }]);
46
+ const lines = csv.split('\n');
47
+ expect(lines[1]).toBe('Test,');
48
+ });
49
+ it('should not include hidden _RecordID/_EntityName columns', () => {
50
+ // Columns array should NOT include _RecordID/_EntityName
51
+ const columns = ['Tag', 'Usage Count'];
52
+ const data = [
53
+ { Tag: 'CRM', 'Usage Count': 42, _RecordID: 'abc-123', _EntityName: 'MJ: Tags' },
54
+ ];
55
+ const csv = buildCSV(columns, data);
56
+ expect(csv).not.toContain('_RecordID');
57
+ expect(csv).not.toContain('abc-123');
58
+ expect(csv).toContain('CRM');
59
+ expect(csv).toContain('42');
60
+ });
61
+ });
62
+ describe('Analytics Resource — Drill-Down Actions (AN-1)', () => {
63
+ it('should include _RecordID and _EntityName in drill-down data for navigable rows', () => {
64
+ // Simulating what loadDrillDownData produces for kpi-totalTags
65
+ const rawTag = { ID: 'tag-1', Name: 'CRM', DisplayName: 'CRM', Description: 'Customer RM', Parent: 'Root' };
66
+ const drillDownRow = {
67
+ 'Name': String(rawTag.Name),
68
+ 'Display Name': String(rawTag.DisplayName),
69
+ 'Description': String(rawTag.Description),
70
+ 'Parent': String(rawTag.Parent),
71
+ '_RecordID': String(rawTag.ID),
72
+ '_EntityName': 'MJ: Tags',
73
+ };
74
+ expect(drillDownRow['_RecordID']).toBe('tag-1');
75
+ expect(drillDownRow['_EntityName']).toBe('MJ: Tags');
76
+ });
77
+ it('should not include _RecordID for aggregate-only drill-downs', () => {
78
+ // contentCoverage and qualityScore don't have individual records
79
+ const coverageRow = {
80
+ 'Content Type': 'Articles',
81
+ 'Total Items': 100,
82
+ 'Tagged Items': 85,
83
+ 'Coverage %': '85%',
84
+ };
85
+ expect(coverageRow).not.toHaveProperty('_RecordID');
86
+ expect(coverageRow).not.toHaveProperty('_EntityName');
87
+ });
88
+ });
89
+ describe('Analytics Resource — Cost Aggregation (D1)', () => {
90
+ // Re-implement the static SumField method from the component for testing
91
+ function sumField(records, fieldName) {
92
+ return records.reduce((sum, r) => sum + Number(r[fieldName] || 0), 0);
93
+ }
94
+ const mockDetails = [
95
+ { ContentProcessRunID: 'run-1', TotalTokensUsed: 1500, TotalCost: 0.0045 },
96
+ { ContentProcessRunID: 'run-1', TotalTokensUsed: 2500, TotalCost: 0.0075 },
97
+ { ContentProcessRunID: 'run-2', TotalTokensUsed: 3000, TotalCost: 0.009 },
98
+ ];
99
+ it('should sum total tokens across all detail records', () => {
100
+ const totalTokens = sumField(mockDetails, 'TotalTokensUsed');
101
+ expect(totalTokens).toBe(7000);
102
+ });
103
+ it('should sum total cost across all detail records', () => {
104
+ const totalCost = sumField(mockDetails, 'TotalCost');
105
+ expect(totalCost).toBeCloseTo(0.021, 4);
106
+ });
107
+ it('should calculate average cost per run', () => {
108
+ const totalCost = sumField(mockDetails, 'TotalCost');
109
+ const runCount = 2; // Two unique runs
110
+ const avgCostPerRun = runCount > 0 ? totalCost / runCount : 0;
111
+ expect(avgCostPerRun).toBeCloseTo(0.0105, 4);
112
+ });
113
+ it('should handle empty records gracefully', () => {
114
+ const totalTokens = sumField([], 'TotalTokensUsed');
115
+ const totalCost = sumField([], 'TotalCost');
116
+ expect(totalTokens).toBe(0);
117
+ expect(totalCost).toBe(0);
118
+ });
119
+ it('should handle records with missing/null fields', () => {
120
+ const records = [
121
+ { ContentProcessRunID: 'run-1', TotalTokensUsed: null, TotalCost: undefined },
122
+ { ContentProcessRunID: 'run-2' },
123
+ ];
124
+ expect(sumField(records, 'TotalTokensUsed')).toBe(0);
125
+ expect(sumField(records, 'TotalCost')).toBe(0);
126
+ });
127
+ it('should filter details by run ID correctly', () => {
128
+ const filteredRunIds = new Set(['run-1']);
129
+ const filteredDetails = mockDetails.filter(d => filteredRunIds.has(String(d['ContentProcessRunID'] || '')));
130
+ const tokens = sumField(filteredDetails, 'TotalTokensUsed');
131
+ expect(tokens).toBe(4000); // 1500 + 2500
132
+ });
133
+ });
134
+ describe('Analytics Resource — Preference Persistence (SR-6)', () => {
135
+ let mockStorage;
136
+ beforeEach(() => {
137
+ mockStorage = {};
138
+ vi.stubGlobal('localStorage', {
139
+ getItem: vi.fn((key) => mockStorage[key] ?? null),
140
+ setItem: vi.fn((key, value) => { mockStorage[key] = value; }),
141
+ removeItem: vi.fn((key) => { delete mockStorage[key]; }),
142
+ });
143
+ });
144
+ afterEach(() => {
145
+ vi.unstubAllGlobals();
146
+ });
147
+ it('should persist preferences to localStorage', () => {
148
+ const prefs = {
149
+ ActiveTab: 'tags',
150
+ ActiveDateRange: '90D',
151
+ EntityFilter: 'Contacts',
152
+ };
153
+ localStorage.setItem('KH_AnalyticsPreferences', JSON.stringify(prefs));
154
+ const stored = JSON.parse(localStorage.getItem('KH_AnalyticsPreferences'));
155
+ expect(stored.ActiveTab).toBe('tags');
156
+ expect(stored.ActiveDateRange).toBe('90D');
157
+ expect(stored.EntityFilter).toBe('Contacts');
158
+ });
159
+ it('should load preferences from localStorage', () => {
160
+ const prefs = { ActiveTab: 'pipeline', ActiveDateRange: '7D', EntityFilter: 'All Entities' };
161
+ mockStorage['KH_AnalyticsPreferences'] = JSON.stringify(prefs);
162
+ const raw = localStorage.getItem('KH_AnalyticsPreferences');
163
+ expect(raw).not.toBeNull();
164
+ const parsed = JSON.parse(raw);
165
+ expect(parsed.ActiveTab).toBe('pipeline');
166
+ });
167
+ it('should handle missing localStorage gracefully', () => {
168
+ const raw = localStorage.getItem('KH_AnalyticsPreferences');
169
+ expect(raw).toBeNull();
170
+ // Component should use defaults when no stored prefs
171
+ });
172
+ it('should store search preferences separately', () => {
173
+ const searchPrefs = { ShowFilters: true, MinScoreThreshold: 0.5 };
174
+ localStorage.setItem('KH_SearchPreferences', JSON.stringify(searchPrefs));
175
+ const analyticsPrefs = localStorage.getItem('KH_AnalyticsPreferences');
176
+ expect(analyticsPrefs).toBeNull(); // Should not cross-pollinate
177
+ const searchStored = JSON.parse(localStorage.getItem('KH_SearchPreferences'));
178
+ expect(searchStored.MinScoreThreshold).toBe(0.5);
179
+ });
180
+ });
181
+ //# sourceMappingURL=analytics-resource.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics-resource.test.js","sourceRoot":"","sources":["../../src/__tests__/analytics-resource.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tests for analytics resource component logic:\n * - AN-1: Drill-down Open Record\n * - AN-3: CSV Export\n * - SR-6: Preference persistence\n */\nimport { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';\n\n// ── Test the pure-logic utility functions extracted from the component ──\n\ndescribe('Analytics Resource — CSV Export (AN-3)', () => {\n // Test the CSV generation logic independently\n function buildCSV(\n columns: string[],\n data: Record<string, string | number | null>[],\n ): string {\n const escape = (v: string | number | null): string => {\n const s = String(v ?? '');\n return s.includes(',') || s.includes('\"') || s.includes('\\n')\n ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n const header = columns.map(escape).join(',');\n const rows = data.map(row => columns.map(c => escape(row[c])).join(','));\n return [header, ...rows].join('\\n');\n }\n\n it('should generate valid CSV header', () => {\n const csv = buildCSV(['Name', 'Count'], []);\n expect(csv).toBe('Name,Count');\n });\n\n it('should generate CSV with data rows', () => {\n const csv = buildCSV(\n ['Name', 'Score'],\n [\n { Name: 'Tag A', Score: 42 },\n { Name: 'Tag B', Score: 18 },\n ]\n );\n const lines = csv.split('\\n');\n expect(lines).toHaveLength(3);\n expect(lines[0]).toBe('Name,Score');\n expect(lines[1]).toBe('Tag A,42');\n expect(lines[2]).toBe('Tag B,18');\n });\n\n it('should escape values containing commas', () => {\n const csv = buildCSV(\n ['Name'],\n [{ Name: 'Doe, John' }]\n );\n expect(csv).toContain('\"Doe, John\"');\n });\n\n it('should escape values containing quotes', () => {\n const csv = buildCSV(\n ['Name'],\n [{ Name: 'He said \"hello\"' }]\n );\n expect(csv).toContain('\"He said \"\"hello\"\"\"');\n });\n\n it('should handle null values', () => {\n const csv = buildCSV(\n ['Name', 'Value'],\n [{ Name: 'Test', Value: null }]\n );\n const lines = csv.split('\\n');\n expect(lines[1]).toBe('Test,');\n });\n\n it('should not include hidden _RecordID/_EntityName columns', () => {\n // Columns array should NOT include _RecordID/_EntityName\n const columns = ['Tag', 'Usage Count'];\n const data = [\n { Tag: 'CRM', 'Usage Count': 42, _RecordID: 'abc-123', _EntityName: 'MJ: Tags' },\n ];\n\n const csv = buildCSV(columns, data);\n expect(csv).not.toContain('_RecordID');\n expect(csv).not.toContain('abc-123');\n expect(csv).toContain('CRM');\n expect(csv).toContain('42');\n });\n});\n\ndescribe('Analytics Resource — Drill-Down Actions (AN-1)', () => {\n it('should include _RecordID and _EntityName in drill-down data for navigable rows', () => {\n // Simulating what loadDrillDownData produces for kpi-totalTags\n const rawTag = { ID: 'tag-1', Name: 'CRM', DisplayName: 'CRM', Description: 'Customer RM', Parent: 'Root' };\n const drillDownRow = {\n 'Name': String(rawTag.Name),\n 'Display Name': String(rawTag.DisplayName),\n 'Description': String(rawTag.Description),\n 'Parent': String(rawTag.Parent),\n '_RecordID': String(rawTag.ID),\n '_EntityName': 'MJ: Tags',\n };\n\n expect(drillDownRow['_RecordID']).toBe('tag-1');\n expect(drillDownRow['_EntityName']).toBe('MJ: Tags');\n });\n\n it('should not include _RecordID for aggregate-only drill-downs', () => {\n // contentCoverage and qualityScore don't have individual records\n const coverageRow = {\n 'Content Type': 'Articles',\n 'Total Items': 100,\n 'Tagged Items': 85,\n 'Coverage %': '85%',\n };\n\n expect(coverageRow).not.toHaveProperty('_RecordID');\n expect(coverageRow).not.toHaveProperty('_EntityName');\n });\n});\n\ndescribe('Analytics Resource — Cost Aggregation (D1)', () => {\n // Re-implement the static SumField method from the component for testing\n function sumField(records: Record<string, unknown>[], fieldName: string): number {\n return records.reduce((sum, r) => sum + Number(r[fieldName] || 0), 0);\n }\n\n const mockDetails: Record<string, unknown>[] = [\n { ContentProcessRunID: 'run-1', TotalTokensUsed: 1500, TotalCost: 0.0045 },\n { ContentProcessRunID: 'run-1', TotalTokensUsed: 2500, TotalCost: 0.0075 },\n { ContentProcessRunID: 'run-2', TotalTokensUsed: 3000, TotalCost: 0.009 },\n ];\n\n it('should sum total tokens across all detail records', () => {\n const totalTokens = sumField(mockDetails, 'TotalTokensUsed');\n expect(totalTokens).toBe(7000);\n });\n\n it('should sum total cost across all detail records', () => {\n const totalCost = sumField(mockDetails, 'TotalCost');\n expect(totalCost).toBeCloseTo(0.021, 4);\n });\n\n it('should calculate average cost per run', () => {\n const totalCost = sumField(mockDetails, 'TotalCost');\n const runCount = 2; // Two unique runs\n const avgCostPerRun = runCount > 0 ? totalCost / runCount : 0;\n expect(avgCostPerRun).toBeCloseTo(0.0105, 4);\n });\n\n it('should handle empty records gracefully', () => {\n const totalTokens = sumField([], 'TotalTokensUsed');\n const totalCost = sumField([], 'TotalCost');\n expect(totalTokens).toBe(0);\n expect(totalCost).toBe(0);\n });\n\n it('should handle records with missing/null fields', () => {\n const records: Record<string, unknown>[] = [\n { ContentProcessRunID: 'run-1', TotalTokensUsed: null, TotalCost: undefined },\n { ContentProcessRunID: 'run-2' },\n ];\n expect(sumField(records, 'TotalTokensUsed')).toBe(0);\n expect(sumField(records, 'TotalCost')).toBe(0);\n });\n\n it('should filter details by run ID correctly', () => {\n const filteredRunIds = new Set(['run-1']);\n const filteredDetails = mockDetails.filter(\n d => filteredRunIds.has(String(d['ContentProcessRunID'] || ''))\n );\n const tokens = sumField(filteredDetails, 'TotalTokensUsed');\n expect(tokens).toBe(4000); // 1500 + 2500\n });\n});\n\ndescribe('Analytics Resource — Preference Persistence (SR-6)', () => {\n let mockStorage: Record<string, string>;\n\n beforeEach(() => {\n mockStorage = {};\n vi.stubGlobal('localStorage', {\n getItem: vi.fn((key: string) => mockStorage[key] ?? null),\n setItem: vi.fn((key: string, value: string) => { mockStorage[key] = value; }),\n removeItem: vi.fn((key: string) => { delete mockStorage[key]; }),\n });\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n it('should persist preferences to localStorage', () => {\n const prefs = {\n ActiveTab: 'tags',\n ActiveDateRange: '90D',\n EntityFilter: 'Contacts',\n };\n localStorage.setItem('KH_AnalyticsPreferences', JSON.stringify(prefs));\n\n const stored = JSON.parse(localStorage.getItem('KH_AnalyticsPreferences')!);\n expect(stored.ActiveTab).toBe('tags');\n expect(stored.ActiveDateRange).toBe('90D');\n expect(stored.EntityFilter).toBe('Contacts');\n });\n\n it('should load preferences from localStorage', () => {\n const prefs = { ActiveTab: 'pipeline', ActiveDateRange: '7D', EntityFilter: 'All Entities' };\n mockStorage['KH_AnalyticsPreferences'] = JSON.stringify(prefs);\n\n const raw = localStorage.getItem('KH_AnalyticsPreferences');\n expect(raw).not.toBeNull();\n const parsed = JSON.parse(raw!);\n expect(parsed.ActiveTab).toBe('pipeline');\n });\n\n it('should handle missing localStorage gracefully', () => {\n const raw = localStorage.getItem('KH_AnalyticsPreferences');\n expect(raw).toBeNull();\n // Component should use defaults when no stored prefs\n });\n\n it('should store search preferences separately', () => {\n const searchPrefs = { ShowFilters: true, MinScoreThreshold: 0.5 };\n localStorage.setItem('KH_SearchPreferences', JSON.stringify(searchPrefs));\n\n const analyticsPrefs = localStorage.getItem('KH_AnalyticsPreferences');\n expect(analyticsPrefs).toBeNull(); // Should not cross-pollinate\n\n const searchStored = JSON.parse(localStorage.getItem('KH_SearchPreferences')!);\n expect(searchStored.MinScoreThreshold).toBe(0.5);\n });\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scheduling.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduling.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/scheduling.test.ts"],"names":[],"mappings":""}