@memberjunction/ng-dashboards 6.2.0-edge.3 → 6.2.0-edge.5

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 (247) hide show
  1. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts +4 -3
  2. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts.map +1 -1
  3. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js +8 -5
  4. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js.map +1 -1
  5. package/dist/AI/components/duplicates/compare-value.component.d.ts +67 -0
  6. package/dist/AI/components/duplicates/compare-value.component.d.ts.map +1 -0
  7. package/dist/AI/components/duplicates/compare-value.component.js +168 -0
  8. package/dist/AI/components/duplicates/compare-value.component.js.map +1 -0
  9. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +49 -3
  10. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
  11. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +287 -169
  12. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
  13. package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts +77 -0
  14. package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts.map +1 -0
  15. package/dist/AI/components/duplicates/duplicate-detection-run-scope.js +116 -0
  16. package/dist/AI/components/duplicates/duplicate-detection-run-scope.js.map +1 -0
  17. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts +1 -0
  18. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts.map +1 -1
  19. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js +16 -4
  20. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js.map +1 -1
  21. package/dist/AI/components/tags/tags-resource.component.d.ts +2 -0
  22. package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
  23. package/dist/AI/components/tags/tags-resource.component.js +12 -6
  24. package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
  25. package/dist/AI/components/vectors/sensitive-fields.d.ts +53 -0
  26. package/dist/AI/components/vectors/sensitive-fields.d.ts.map +1 -0
  27. package/dist/AI/components/vectors/sensitive-fields.js +226 -0
  28. package/dist/AI/components/vectors/sensitive-fields.js.map +1 -0
  29. package/dist/AI/components/vectors/vector-document-rules.d.ts +126 -0
  30. package/dist/AI/components/vectors/vector-document-rules.d.ts.map +1 -0
  31. package/dist/AI/components/vectors/vector-document-rules.js +168 -0
  32. package/dist/AI/components/vectors/vector-document-rules.js.map +1 -0
  33. package/dist/AI/components/vectors/vector-index-auto.d.ts +29 -0
  34. package/dist/AI/components/vectors/vector-index-auto.d.ts.map +1 -0
  35. package/dist/AI/components/vectors/vector-index-auto.js +59 -0
  36. package/dist/AI/components/vectors/vector-index-auto.js.map +1 -0
  37. package/dist/AI/components/vectors/vector-management-resource.component.d.ts +58 -0
  38. package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
  39. package/dist/AI/components/vectors/vector-management-resource.component.js +493 -250
  40. package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
  41. package/dist/Actions/components/actions-overview.component.d.ts.map +1 -1
  42. package/dist/Actions/components/actions-overview.component.js +7 -3
  43. package/dist/Actions/components/actions-overview.component.js.map +1 -1
  44. package/dist/Actions/components/categories-list-view.component.d.ts.map +1 -1
  45. package/dist/Actions/components/categories-list-view.component.js +3 -2
  46. package/dist/Actions/components/categories-list-view.component.js.map +1 -1
  47. package/dist/Communication/communication-agent-context.d.ts +11 -1
  48. package/dist/Communication/communication-agent-context.d.ts.map +1 -1
  49. package/dist/Communication/communication-agent-context.js +15 -0
  50. package/dist/Communication/communication-agent-context.js.map +1 -1
  51. package/dist/Communication/communication-dashboard.component.d.ts.map +1 -1
  52. package/dist/Communication/communication-dashboard.component.js +5 -0
  53. package/dist/Communication/communication-dashboard.component.js.map +1 -1
  54. package/dist/Communication/communication-providers-resource.component.d.ts +27 -1
  55. package/dist/Communication/communication-providers-resource.component.d.ts.map +1 -1
  56. package/dist/Communication/communication-providers-resource.component.js +722 -78
  57. package/dist/Communication/communication-providers-resource.component.js.map +1 -1
  58. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
  59. package/dist/ComponentStudio/component-studio-dashboard.component.js +1 -0
  60. package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
  61. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +0 -3
  62. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
  63. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +5 -14
  64. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
  65. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
  66. package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -0
  67. package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
  68. package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts.map +1 -1
  69. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +1 -0
  70. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
  71. package/dist/Credentials/components/credentials-list-resource.component.d.ts +2 -0
  72. package/dist/Credentials/components/credentials-list-resource.component.d.ts.map +1 -1
  73. package/dist/Credentials/components/credentials-list-resource.component.js +12 -10
  74. package/dist/Credentials/components/credentials-list-resource.component.js.map +1 -1
  75. package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts +14 -43
  76. package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts.map +1 -1
  77. package/dist/DashboardBrowser/dashboard-browser-agent-context.js +6 -29
  78. package/dist/DashboardBrowser/dashboard-browser-agent-context.js.map +1 -1
  79. package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts +174 -321
  80. package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts.map +1 -1
  81. package/dist/DashboardBrowser/dashboard-browser-resource.component.js +522 -1195
  82. package/dist/DashboardBrowser/dashboard-browser-resource.component.js.map +1 -1
  83. package/dist/DashboardBrowser/dashboard-library-filter.d.ts +116 -0
  84. package/dist/DashboardBrowser/dashboard-library-filter.d.ts.map +1 -0
  85. package/dist/DashboardBrowser/dashboard-library-filter.js +277 -0
  86. package/dist/DashboardBrowser/dashboard-library-filter.js.map +1 -0
  87. package/dist/DashboardBrowser/dashboard-share-dialog.component.d.ts +1 -1
  88. package/dist/DashboardBrowser/dashboard-share-dialog.component.js.map +1 -1
  89. package/dist/DashboardBrowser/dashboards-agent-tools.d.ts +26 -0
  90. package/dist/DashboardBrowser/dashboards-agent-tools.d.ts.map +1 -0
  91. package/dist/DashboardBrowser/dashboards-agent-tools.js +32 -0
  92. package/dist/DashboardBrowser/dashboards-agent-tools.js.map +1 -0
  93. package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts +58 -0
  94. package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts.map +1 -0
  95. package/dist/DashboardBrowser/dashboards-categories-resource.component.js +391 -0
  96. package/dist/DashboardBrowser/dashboards-categories-resource.component.js.map +1 -0
  97. package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts +37 -0
  98. package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts.map +1 -0
  99. package/dist/DashboardBrowser/dashboards-categories.helpers.js +68 -0
  100. package/dist/DashboardBrowser/dashboards-categories.helpers.js.map +1 -0
  101. package/dist/DashboardEditor/dashboard-editor.component.d.ts +295 -0
  102. package/dist/DashboardEditor/dashboard-editor.component.d.ts.map +1 -0
  103. package/dist/DashboardEditor/dashboard-editor.component.js +1022 -0
  104. package/dist/DashboardEditor/dashboard-editor.component.js.map +1 -0
  105. package/dist/DashboardEditor/dashboard-editor.types.d.ts +23 -0
  106. package/dist/DashboardEditor/dashboard-editor.types.d.ts.map +1 -0
  107. package/dist/DashboardEditor/dashboard-editor.types.js +3 -0
  108. package/dist/DashboardEditor/dashboard-editor.types.js.map +1 -0
  109. package/dist/DashboardEditor/dashboard-viewer-factory.d.ts +11 -0
  110. package/dist/DashboardEditor/dashboard-viewer-factory.d.ts.map +1 -0
  111. package/dist/DashboardEditor/dashboard-viewer-factory.js +17 -0
  112. package/dist/DashboardEditor/dashboard-viewer-factory.js.map +1 -0
  113. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +51 -0
  114. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
  115. package/dist/DataExplorer/data-explorer-dashboard.component.js +375 -223
  116. package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
  117. package/dist/DataExplorer/services/explorer-state.service.d.ts +1 -1
  118. package/dist/DataExplorer/services/explorer-state.service.js +2 -5
  119. package/dist/DataExplorer/services/explorer-state.service.js.map +1 -1
  120. package/dist/DevTools/app-state-inspector.component.d.ts.map +1 -1
  121. package/dist/DevTools/app-state-inspector.component.js +1 -0
  122. package/dist/DevTools/app-state-inspector.component.js.map +1 -1
  123. package/dist/DevTools/class-registry.component.d.ts.map +1 -1
  124. package/dist/DevTools/class-registry.component.js +1 -0
  125. package/dist/DevTools/class-registry.component.js.map +1 -1
  126. package/dist/DevTools/event-monitor.component.d.ts.map +1 -1
  127. package/dist/DevTools/event-monitor.component.js +2 -0
  128. package/dist/DevTools/event-monitor.component.js.map +1 -1
  129. package/dist/DevTools/graphql-console.component.d.ts.map +1 -1
  130. package/dist/DevTools/graphql-console.component.js.map +1 -1
  131. package/dist/DevTools/grid-width-lab.component.d.ts.map +1 -1
  132. package/dist/DevTools/grid-width-lab.component.js +1 -0
  133. package/dist/DevTools/grid-width-lab.component.js.map +1 -1
  134. package/dist/DevTools/layout-inspector.component.d.ts.map +1 -1
  135. package/dist/DevTools/layout-inspector.component.js +1 -0
  136. package/dist/DevTools/layout-inspector.component.js.map +1 -1
  137. package/dist/DevTools/lazy-module-status.component.d.ts.map +1 -1
  138. package/dist/DevTools/lazy-module-status.component.js +1 -0
  139. package/dist/DevTools/lazy-module-status.component.js.map +1 -1
  140. package/dist/DevTools/settings-explorer.component.d.ts.map +1 -1
  141. package/dist/DevTools/settings-explorer.component.js +1 -0
  142. package/dist/DevTools/settings-explorer.component.js.map +1 -1
  143. package/dist/DevTools/tab-strip-lab.component.d.ts.map +1 -1
  144. package/dist/DevTools/tab-strip-lab.component.js +1 -0
  145. package/dist/DevTools/tab-strip-lab.component.js.map +1 -1
  146. package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts +5 -0
  147. package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts.map +1 -1
  148. package/dist/EntityAdmin/entity-admin-dashboard.component.js +8 -5
  149. package/dist/EntityAdmin/entity-admin-dashboard.component.js.map +1 -1
  150. package/dist/FormBuilder/form-builder-resource.component.d.ts +0 -14
  151. package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -1
  152. package/dist/FormBuilder/form-builder-resource.component.js +7 -29
  153. package/dist/FormBuilder/form-builder-resource.component.js.map +1 -1
  154. package/dist/Home/home-agent-context.d.ts +29 -10
  155. package/dist/Home/home-agent-context.d.ts.map +1 -1
  156. package/dist/Home/home-agent-context.js +50 -24
  157. package/dist/Home/home-agent-context.js.map +1 -1
  158. package/dist/Home/home-dashboard-switcher.component.d.ts +95 -0
  159. package/dist/Home/home-dashboard-switcher.component.d.ts.map +1 -0
  160. package/dist/Home/home-dashboard-switcher.component.js +442 -0
  161. package/dist/Home/home-dashboard-switcher.component.js.map +1 -0
  162. package/dist/Home/home-dashboard.component.d.ts +330 -17
  163. package/dist/Home/home-dashboard.component.d.ts.map +1 -1
  164. package/dist/Home/home-dashboard.component.js +1702 -1014
  165. package/dist/Home/home-dashboard.component.js.map +1 -1
  166. package/dist/Home/home-menus.d.ts +38 -0
  167. package/dist/Home/home-menus.d.ts.map +1 -0
  168. package/dist/Home/home-menus.js +53 -0
  169. package/dist/Home/home-menus.js.map +1 -0
  170. package/dist/Home/home-pinned-dashboards.d.ts +33 -0
  171. package/dist/Home/home-pinned-dashboards.d.ts.map +1 -0
  172. package/dist/Home/home-pinned-dashboards.js +76 -0
  173. package/dist/Home/home-pinned-dashboards.js.map +1 -0
  174. package/dist/Integration/components/overview/overview.component.js +2 -2
  175. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +2 -2
  176. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -1
  177. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts +11 -1
  178. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts.map +1 -1
  179. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js +24 -2
  180. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js.map +1 -1
  181. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.d.ts +16 -2
  182. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.d.ts.map +1 -1
  183. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js +87 -33
  184. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js.map +1 -1
  185. package/dist/LoginStudio/login-studio-inspector.component.d.ts +130 -0
  186. package/dist/LoginStudio/login-studio-inspector.component.d.ts.map +1 -0
  187. package/dist/LoginStudio/login-studio-inspector.component.js +781 -0
  188. package/dist/LoginStudio/login-studio-inspector.component.js.map +1 -0
  189. package/dist/LoginStudio/login-studio-resource.component.d.ts +153 -0
  190. package/dist/LoginStudio/login-studio-resource.component.d.ts.map +1 -0
  191. package/dist/LoginStudio/login-studio-resource.component.js +857 -0
  192. package/dist/LoginStudio/login-studio-resource.component.js.map +1 -0
  193. package/dist/PredictiveStudio/components/ps-production.component.d.ts +5 -1
  194. package/dist/PredictiveStudio/components/ps-production.component.d.ts.map +1 -1
  195. package/dist/PredictiveStudio/components/ps-production.component.js +64 -41
  196. package/dist/PredictiveStudio/components/ps-production.component.js.map +1 -1
  197. package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
  198. package/dist/QueryBrowser/query-browser-resource.component.js +3 -2
  199. package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
  200. package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js +2 -2
  201. package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js.map +1 -1
  202. package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts +1 -0
  203. package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts.map +1 -1
  204. package/dist/TelephonyOperations/telephony-operations-dashboard.component.js +7 -0
  205. package/dist/TelephonyOperations/telephony-operations-dashboard.component.js.map +1 -1
  206. package/dist/Testing/components/testing-explorer.component.d.ts.map +1 -1
  207. package/dist/Testing/components/testing-explorer.component.js +7 -7
  208. package/dist/Testing/components/testing-explorer.component.js.map +1 -1
  209. package/dist/Workflows/components/workflow-runs-resource.component.js +4 -4
  210. package/dist/Workflows/components/workflow-runs-resource.component.js.map +1 -1
  211. package/dist/ai-dashboards.module.d.ts +21 -20
  212. package/dist/ai-dashboards.module.d.ts.map +1 -1
  213. package/dist/ai-dashboards.module.js +4 -1
  214. package/dist/ai-dashboards.module.js.map +1 -1
  215. package/dist/core-dashboards.module.d.ts +70 -62
  216. package/dist/core-dashboards.module.d.ts.map +1 -1
  217. package/dist/core-dashboards.module.js +33 -5
  218. package/dist/core-dashboards.module.js.map +1 -1
  219. package/dist/data-explorer-dashboards.module.d.ts +1 -1
  220. package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
  221. package/dist/data-explorer-dashboards.module.js +3 -1
  222. package/dist/data-explorer-dashboards.module.js.map +1 -1
  223. package/dist/public-api.d.ts +3 -0
  224. package/dist/public-api.d.ts.map +1 -1
  225. package/dist/public-api.js +4 -0
  226. package/dist/public-api.js.map +1 -1
  227. package/dist/shared/dashboard-library-changes.d.ts +23 -0
  228. package/dist/shared/dashboard-library-changes.d.ts.map +1 -0
  229. package/dist/shared/dashboard-library-changes.js +26 -0
  230. package/dist/shared/dashboard-library-changes.js.map +1 -0
  231. package/dist/shared/dashboard-owner-names.d.ts +17 -0
  232. package/dist/shared/dashboard-owner-names.d.ts.map +1 -0
  233. package/dist/shared/dashboard-owner-names.js +65 -0
  234. package/dist/shared/dashboard-owner-names.js.map +1 -0
  235. package/dist/shared/dashboard-recents.d.ts +34 -0
  236. package/dist/shared/dashboard-recents.d.ts.map +1 -0
  237. package/dist/shared/dashboard-recents.js +87 -0
  238. package/dist/shared/dashboard-recents.js.map +1 -0
  239. package/dist/shared/dashboards-app-install.d.ts +59 -0
  240. package/dist/shared/dashboards-app-install.d.ts.map +1 -0
  241. package/dist/shared/dashboards-app-install.js +97 -0
  242. package/dist/shared/dashboards-app-install.js.map +1 -0
  243. package/dist/shared/dashboards-app.helpers.d.ts +42 -0
  244. package/dist/shared/dashboards-app.helpers.d.ts.map +1 -0
  245. package/dist/shared/dashboards-app.helpers.js +143 -0
  246. package/dist/shared/dashboards-app.helpers.js.map +1 -0
  247. package/package.json +73 -69
@@ -1,5 +1,6 @@
1
1
  import { BaseEngine, IMetadataProvider, UserInfo } from '@memberjunction/core';
2
2
  import { MJRecordProcessEntity, MJProcessRunEntity } from '@memberjunction/core-entities';
3
+ import { type FeaturePipelineRunResult } from '@memberjunction/feature-pipelines';
3
4
  import type { Observable } from 'rxjs';
4
5
  /**
5
6
  * Name of the seeded `MJ: Record Process Categories` row that marks a Record
@@ -55,6 +56,10 @@ export interface FeaturePipelineSummary {
55
56
  LastRunSuccess: number | null;
56
57
  /** Records that errored in the most recent run, or null. */
57
58
  LastRunErrors: number | null;
59
+ /** What the most recent run did with its records ({@link FeaturePipelineEngine.DescribeRunOutcome}), or null for a run without a result. */
60
+ LastRunOutcomeText: string | null;
61
+ /** The most recent run's error message when it did not complete (a refused or failed run), else null. */
62
+ LastRunErrorMessage: string | null;
58
63
  /**
59
64
  * The pipeline type ('LLM' or 'Decision' or custom). Defaults to 'LLM' when unset. The two built-in
60
65
  * names are given in their canonical case whatever case the spec uses, as the processor matches them.
@@ -109,7 +114,12 @@ export declare class FeaturePipelineEngine extends BaseEngine<FeaturePipelineEng
109
114
  private static isFeaturePipeline;
110
115
  /** Project one pipeline + the run set into a {@link FeaturePipelineSummary}. */
111
116
  private static buildSummary;
112
- /** Newest run for a pipeline id, scanning by StartTime/EndTime/createdAt regardless of input order. */
117
+ /**
118
+ * The card's last-run line from a run's result. Records skipped on their watermark count as unchanged: they still
119
+ * hold what the pipeline wrote. A completed run with nothing to write is "up to date".
120
+ */
121
+ static DescribeRunOutcome(result: FeaturePipelineRunResult, run: MJProcessRunEntity): string;
122
+ /** Newest real (not dry) run for a pipeline id, scanning by StartTime/EndTime/createdAt regardless of input order. */
113
123
  private static newestRun;
114
124
  /** Normalize a Process Run status into the small union the UI renders. `undefined` → `'Never'`. */
115
125
  private static mapRunStatus;
@@ -1 +1 @@
1
- {"version":3,"file":"feature-pipeline.engine.d.ts","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"feature-pipeline.engine.d.ts","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { BaseEngine, Metadata, } from '@memberjunction/core';
2
2
  import { SafeJSONParse, UUIDsEqual } from '@memberjunction/global';
3
+ import { FeaturePipelineRunResults } from '@memberjunction/feature-pipelines';
3
4
  /**
4
5
  * Name of the seeded `MJ: Record Process Categories` row that marks a Record
5
6
  * Process as a Feature Pipeline. Authored in
@@ -86,6 +87,7 @@ export class FeaturePipelineEngine extends BaseEngine {
86
87
  /** Project one pipeline + the run set into a {@link FeaturePipelineSummary}. */
87
88
  static buildSummary(p, runs) {
88
89
  const latest = FeaturePipelineEngine.newestRun(runs, p.ID);
90
+ const result = FeaturePipelineRunResults.Parse(latest?.ResultSummary);
89
91
  return {
90
92
  ID: p.ID,
91
93
  Name: p.Name,
@@ -103,15 +105,35 @@ export class FeaturePipelineEngine extends BaseEngine {
103
105
  LastRunProcessed: latest?.ProcessedItems ?? null,
104
106
  LastRunSuccess: latest?.SuccessCount ?? null,
105
107
  LastRunErrors: latest?.ErrorCount ?? null,
108
+ LastRunOutcomeText: latest && result ? FeaturePipelineEngine.DescribeRunOutcome(result, latest) : null,
109
+ LastRunErrorMessage: latest && latest.Status !== 'Completed' ? latest.ErrorMessage || null : null,
106
110
  PipelineType: FeaturePipelineEngine.derivePipelineType(p.Configuration),
107
111
  };
108
112
  }
109
- /** Newest run for a pipeline id, scanning by StartTime/EndTime/createdAt regardless of input order. */
113
+ /**
114
+ * The card's last-run line from a run's result. Records skipped on their watermark count as unchanged: they still
115
+ * hold what the pipeline wrote. A completed run with nothing to write is "up to date".
116
+ */
117
+ static DescribeRunOutcome(result, run) {
118
+ const total = run.ProcessedItems || 0;
119
+ const failed = run.ErrorCount || 0;
120
+ const unchanged = result.Unchanged + result.Skipped;
121
+ if (total === 0) {
122
+ return 'no records processed';
123
+ }
124
+ if (run.Status === 'Completed' && result.Upserted === 0 && failed === 0) {
125
+ return `${unchanged} of ${total} up to date (${unchanged} unchanged)`;
126
+ }
127
+ const parts = [[result.Upserted, 'written'], [unchanged, 'unchanged'], [failed, 'failed']];
128
+ const shown = parts.filter(([count]) => count > 0).map(([count, label]) => `${count} ${label}`);
129
+ return `${shown.length > 0 ? shown.join(', ') : '0 written'} of ${total}`;
130
+ }
131
+ /** Newest real (not dry) run for a pipeline id, scanning by StartTime/EndTime/createdAt regardless of input order. */
110
132
  static newestRun(runs, recordProcessID) {
111
133
  let best;
112
134
  let bestTime = -Infinity;
113
135
  for (const r of runs) {
114
- if (r.RecordProcessID == null || !UUIDsEqual(r.RecordProcessID, recordProcessID))
136
+ if (r.RecordProcessID == null || !UUIDsEqual(r.RecordProcessID, recordProcessID) || r.DryRun)
115
137
  continue;
116
138
  const candidate = r.StartTime ?? r.EndTime ?? r.__mj_CreatedAt;
117
139
  const t = candidate ? new Date(candidate).getTime() : -Infinity;
@@ -1 +1 @@
1
- {"version":3,"file":"feature-pipeline.engine.js","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAGV,QAAQ,GAET,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAInE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,kBAAkB,CAAC;AA2DjE,kEAAkE;AAClE,MAAM,uBAAuB,GAA2B,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AAE7F;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,qBAAsB,SAAQ,UAAiC;IAC1E,+EAA+E;IACxE,MAAM,KAAK,QAAQ;QACxB,OAAO,KAAK,CAAC,WAAW,EAAyB,CAAC;IACpD,CAAC;IAEO,gBAAgB,GAA4B,EAAE,CAAC;IAC/C,YAAY,GAAyB,EAAE,CAAC;IAEhD;;;;;OAKG;IACI,KAAK,CAAC,MAAM,CACjB,YAAsB,EACtB,WAAsB,EACtB,QAA4B;QAE5B,MAAM,CAAC,GAAwC;YAC7C,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,sBAAsB,EAAE,YAAY,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE;YACzG,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,cAAc,EAAE,OAAO,EAAE,qBAAqB,EAAE;SACjH,CAAC;QACF,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,IAAI,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IAChF,CAAC;IAED,kFAAkF;IAElF,qEAAqE;IACrE,IAAW,SAAS;QAClB,OAAO,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,qBAAqB,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC;IACnG,CAAC;IAED,oFAAoF;IACpF,IAAW,IAAI;QACb,OAAO,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;IACjC,CAAC;IAED,mGAAmG;IACnG,IAAW,UAAU;QACnB,OAAO,IAAI,CAAC,eAAe,CAAwB,kBAAkB,CAAC,CAAC;IACzE,CAAC;IAED,kDAAkD;IAClD,IAAW,KAAK;QACd,OAAO,IAAI,CAAC,eAAe,CAAqB,cAAc,CAAC,CAAC;IAClE,CAAC;IAED,uBAAuB;IAEvB,gFAAgF;IACzE,YAAY;QACjB,OAAO,qBAAqB,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,cAAc,CAC1B,SAA+C,EAC/C,IAAuC;QAEvC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,6BAA6B;IAE7B,gFAAgF;IACxE,MAAM,CAAC,iBAAiB,CAAC,EAAyB;QACxD,OAAO,CAAC,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,8BAA8B,CAAC,WAAW,EAAE,CAAC;IACnG,CAAC;IAED,gFAAgF;IACxE,MAAM,CAAC,YAAY,CACzB,CAAwB,EACxB,IAAuC;QAEvC,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3D,OAAO;YACL,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,IAAI;YAClC,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,cAAc,EAAE,CAAC,CAAC,QAAQ;YAC1B,YAAY,EAAE,CAAC,CAAC,MAAM;YACtB,eAAe,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,CAAC,CAAC,aAAa,CAAC;YAC7E,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,SAAS,EAAE,MAAM,EAAE,SAAS,IAAI,MAAM,EAAE,OAAO,IAAI,IAAI;YACvD,aAAa,EAAE,qBAAqB,CAAC,YAAY,CAAC,MAAM,CAAC;YACzD,mBAAmB,EAAE,MAAM,EAAE,EAAE,IAAI,IAAI;YACvC,gBAAgB,EAAE,MAAM,EAAE,cAAc,IAAI,IAAI;YAChD,cAAc,EAAE,MAAM,EAAE,YAAY,IAAI,IAAI;YAC5C,aAAa,EAAE,MAAM,EAAE,UAAU,IAAI,IAAI;YACzC,YAAY,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAC,CAAC,aAAa,CAAC;SACxE,CAAC;IACJ,CAAC;IAED,uGAAuG;IAC/F,MAAM,CAAC,SAAS,CACtB,IAAuC,EACvC,eAAuB;QAEvB,IAAI,IAAoC,CAAC;QACzC,IAAI,QAAQ,GAAG,CAAC,QAAQ,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC,CAAC,eAAe,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC;gBAAE,SAAS;YAC3F,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,cAAc,CAAC;YAC/D,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;YAChE,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;gBACjB,QAAQ,GAAG,CAAC,CAAC;gBACb,IAAI,GAAG,CAAC,CAAC;YACX,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,mGAAmG;IAC3F,MAAM,CAAC,YAAY,CAAC,GAAmC;QAC7D,IAAI,CAAC,GAAG;YAAE,OAAO,OAAO,CAAC;QACzB,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;YACnB,KAAK,WAAW;gBACd,OAAO,WAAW,CAAC;YACrB,KAAK,QAAQ;gBACX,OAAO,QAAQ,CAAC;YAClB,KAAK,WAAW;gBACd,OAAO,WAAW,CAAC;YACrB,KAAK,SAAS,CAAC;YACf,KAAK,SAAS,CAAC;YACf,KAAK,QAAQ;gBACX,OAAO,SAAS,CAAC;YACnB;gBACE,OAAO,OAAO,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,qBAAqB,CAAC,aAA4B;QAC/D,IAAI,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC;QAChC,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAiC,CAAC;QAE9C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;QACtE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE;YAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;QAEtE,MAAM,KAAK,GAAG,qBAAqB,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,CAAC;IAED,0EAA0E;IAClE,MAAM,CAAC,cAAc,CAAC,MAAe;QAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzF,CAAC;QACD,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACjD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAiC,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,kBAAkB,CAAC,MAAqB;QACrD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,aAAa,CAA4B,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnI,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,uBAAuB,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,QAAQ,CAAC;IACrE,CAAC;CACF","sourcesContent":["import {\n BaseEngine,\n BaseEnginePropertyConfig,\n IMetadataProvider,\n Metadata,\n UserInfo,\n} from '@memberjunction/core';\nimport { SafeJSONParse, UUIDsEqual } from '@memberjunction/global';\nimport { MJRecordProcessEntity, MJProcessRunEntity } from '@memberjunction/core-entities';\nimport type { Observable } from 'rxjs';\n\n/**\n * Name of the seeded `MJ: Record Process Categories` row that marks a Record\n * Process as a Feature Pipeline. Authored in\n * `metadata/record-process-categories/.record-process-categories.json` and pushed\n * with `mj sync`. Mirrors `FEATURE_PIPELINE_CATEGORY_NAME` in the server-side\n * `@memberjunction/predictive-studio` engine — kept in sync by value (the two\n * packages don't share a dependency, so the constant is intentionally duplicated\n * rather than re-exported across the package boundary).\n */\nexport const FEATURE_PIPELINE_CATEGORY_NAME = 'Feature Pipeline';\n\n/** Normalized last-run status for a Feature Pipeline, derived from the most recent Process Run. */\nexport type FeaturePipelineRunStatus = 'Never' | 'Running' | 'Completed' | 'Failed' | 'Cancelled';\n\n/**\n * UI-facing summary of one Feature Pipeline: what it is, the entity it writes to,\n * the attribute it produces, and last-run / freshness. Built purely from the\n * cached Record Process + Process Run entities by {@link FeaturePipelineEngine.GetSummaries}.\n */\nexport interface FeaturePipelineSummary {\n /** The underlying `MJ: Record Processes` id (also the `RunFeaturePipeline` `featurePipelineID`). */\n ID: string;\n /** Display name (the Record Process name). */\n Name: string;\n /** Optional description. */\n Description: string | null;\n /** Record Process lifecycle status. */\n Status: 'Active' | 'Disabled' | 'Draft';\n /**\n * The per-record work the pipeline performs. Derived from the entity's `WorkType` so it tracks\n * the CodeGen-generated union (sourced from the `CK_RecordProcess_WorkType` CHECK constraint) and\n * never drifts as work types are added (e.g. `'ML Model'`).\n */\n WorkType: MJRecordProcessEntity['WorkType'];\n /** Id of the target entity whose records the pipeline derives features for. */\n TargetEntityID: string;\n /** Display name of the target entity. */\n TargetEntity: string;\n /** Best-effort attribute written back, parsed from the Record Process `OutputMapping`. */\n OutputAttribute: string | null;\n /** Whether the pipeline can be run on demand (the Run button is enabled). */\n OnDemandEnabled: boolean;\n /** Whether the pipeline runs on a cron schedule. */\n ScheduleEnabled: boolean;\n /** Timestamp of the most recent run (start time, falling back to end time), or null. */\n LastRunAt: Date | null;\n /** Normalized status of the most recent run. */\n LastRunStatus: FeaturePipelineRunStatus;\n /** Id of the most recent `MJ: Process Runs` row, or null. */\n LastRunProcessRunID: string | null;\n /** Records processed in the most recent run, or null. */\n LastRunProcessed: number | null;\n /** Records whose features were written in the most recent run, or null. */\n LastRunSuccess: number | null;\n /** Records that errored in the most recent run, or null. */\n LastRunErrors: number | null;\n /**\n * The pipeline type ('LLM' or 'Decision' or custom). Defaults to 'LLM' when unset. The two built-in\n * names are given in their canonical case whatever case the spec uses, as the processor matches them.\n */\n PipelineType: string;\n}\n\n/** The one DataFeatureSpec property the summary reads from a Record Process `Configuration`. */\ninterface ConfigurationPipelineType {\n PipelineType?: string | null;\n}\n\n/** The built-in pipeline type names, by their lower-case form. */\nconst BUILT_IN_PIPELINE_TYPES: Record<string, string> = { llm: 'LLM', decision: 'Decision' };\n\n/**\n * **FeaturePipelineEngine** (Knowledge Hub, Angular) — the discovery + monitoring\n * cache for Feature Pipelines (Predictive Studio SP6).\n *\n * A Feature Pipeline has no dedicated entity: it IS an `MJ: Record Processes` row\n * categorized into the seeded {@link FEATURE_PIPELINE_CATEGORY_NAME} category. This\n * engine caches those rows (plus the recent `MJ: Process Runs` headers) so the KH\n * UI can list \"what feature pipelines exist, what entity each writes to, and when\n * each last ran\" — reactively, via the canonical MJ BaseEngine pattern (`Config()`\n * declaring `BaseEnginePropertyConfig[]`, lazy-loaded, `ObserveProperty`\n * reactivity for free).\n *\n * The freshness/projection logic is a pure static function ({@link BuildSummaries})\n * so it can be exercised without a provider, and it intentionally mirrors the\n * server-side `@memberjunction/predictive-studio` engine of the same name (the two\n * don't share a dependency).\n */\nexport class FeaturePipelineEngine extends BaseEngine<FeaturePipelineEngine> {\n /** Singleton accessor for the default provider — never `new` this directly. */\n public static get Instance(): FeaturePipelineEngine {\n return super.getInstance<FeaturePipelineEngine>();\n }\n\n private _recordProcesses: MJRecordProcessEntity[] = [];\n private _processRuns: MJProcessRunEntity[] = [];\n\n /**\n * Lazy-load the Record Process + recent Process Run caches. Safe to call from\n * every entry point — a no-op when already loaded (unless `forceRefresh`).\n * Process Runs are ordered newest-first so the most recent run per pipeline is\n * the first match.\n */\n public async Config(\n forceRefresh?: boolean,\n contextUser?: UserInfo,\n provider?: IMetadataProvider,\n ): Promise<void> {\n const c: Partial<BaseEnginePropertyConfig>[] = [\n { Type: 'entity', EntityName: 'MJ: Record Processes', PropertyName: '_recordProcesses', OrderBy: 'Name' },\n { Type: 'entity', EntityName: 'MJ: Process Runs', PropertyName: '_processRuns', OrderBy: '__mj_CreatedAt DESC' },\n ];\n await super.Load(c, provider ?? Metadata.Provider, forceRefresh, contextUser);\n }\n\n // ---- Cached arrays (sync getters; observe via the streams for reactive UI) ----\n\n /** Every cached Record Process categorized as a Feature Pipeline. */\n public get Pipelines(): MJRecordProcessEntity[] {\n return (this._recordProcesses ?? []).filter((rp) => FeaturePipelineEngine.isFeaturePipeline(rp));\n }\n\n /** All cached Process Run headers (newest first; all entities — callers filter). */\n public get Runs(): MJProcessRunEntity[] {\n return this._processRuns ?? [];\n }\n\n /** Reactive stream of the raw Record Processes — re-emits on any save/delete/remote-invalidate. */\n public get Pipelines$(): Observable<MJRecordProcessEntity[]> {\n return this.ObserveProperty<MJRecordProcessEntity>('_recordProcesses');\n }\n\n /** Reactive stream of the Process Run headers. */\n public get Runs$(): Observable<MJProcessRunEntity[]> {\n return this.ObserveProperty<MJProcessRunEntity>('_processRuns');\n }\n\n // ---- Projection ----\n\n /** Build the UI summaries for every feature pipeline from the loaded caches. */\n public GetSummaries(): FeaturePipelineSummary[] {\n return FeaturePipelineEngine.BuildSummaries(this.Pipelines, this.Runs);\n }\n\n /**\n * Pure, side-effect-free summary builder — used by {@link GetSummaries} and by\n * unit tests (which pass in-memory arrays, no provider).\n */\n public static BuildSummaries(\n pipelines: ReadonlyArray<MJRecordProcessEntity>,\n runs: ReadonlyArray<MJProcessRunEntity>,\n ): FeaturePipelineSummary[] {\n return pipelines.map((p) => FeaturePipelineEngine.buildSummary(p, runs));\n }\n\n // ---- Internal helpers ----\n\n /** Whether a Record Process belongs to the seeded Feature Pipeline category. */\n private static isFeaturePipeline(rp: MJRecordProcessEntity): boolean {\n return (rp.Category ?? '').trim().toLowerCase() === FEATURE_PIPELINE_CATEGORY_NAME.toLowerCase();\n }\n\n /** Project one pipeline + the run set into a {@link FeaturePipelineSummary}. */\n private static buildSummary(\n p: MJRecordProcessEntity,\n runs: ReadonlyArray<MJProcessRunEntity>,\n ): FeaturePipelineSummary {\n const latest = FeaturePipelineEngine.newestRun(runs, p.ID);\n return {\n ID: p.ID,\n Name: p.Name,\n Description: p.Description ?? null,\n Status: p.Status,\n WorkType: p.WorkType,\n TargetEntityID: p.EntityID,\n TargetEntity: p.Entity,\n OutputAttribute: FeaturePipelineEngine.deriveOutputAttribute(p.OutputMapping),\n OnDemandEnabled: p.OnDemandEnabled,\n ScheduleEnabled: p.ScheduleEnabled,\n LastRunAt: latest?.StartTime ?? latest?.EndTime ?? null,\n LastRunStatus: FeaturePipelineEngine.mapRunStatus(latest),\n LastRunProcessRunID: latest?.ID ?? null,\n LastRunProcessed: latest?.ProcessedItems ?? null,\n LastRunSuccess: latest?.SuccessCount ?? null,\n LastRunErrors: latest?.ErrorCount ?? null,\n PipelineType: FeaturePipelineEngine.derivePipelineType(p.Configuration),\n };\n }\n\n /** Newest run for a pipeline id, scanning by StartTime/EndTime/createdAt regardless of input order. */\n private static newestRun(\n runs: ReadonlyArray<MJProcessRunEntity>,\n recordProcessID: string,\n ): MJProcessRunEntity | undefined {\n let best: MJProcessRunEntity | undefined;\n let bestTime = -Infinity;\n for (const r of runs) {\n if (r.RecordProcessID == null || !UUIDsEqual(r.RecordProcessID, recordProcessID)) continue;\n const candidate = r.StartTime ?? r.EndTime ?? r.__mj_CreatedAt;\n const t = candidate ? new Date(candidate).getTime() : -Infinity;\n if (t > bestTime) {\n bestTime = t;\n best = r;\n }\n }\n return best;\n }\n\n /** Normalize a Process Run status into the small union the UI renders. `undefined` → `'Never'`. */\n private static mapRunStatus(run: MJProcessRunEntity | undefined): FeaturePipelineRunStatus {\n if (!run) return 'Never';\n switch (run.Status) {\n case 'Completed':\n return 'Completed';\n case 'Failed':\n return 'Failed';\n case 'Cancelled':\n return 'Cancelled';\n case 'Running':\n case 'Pending':\n case 'Paused':\n return 'Running';\n default:\n return 'Never';\n }\n }\n\n /**\n * Best-effort attribute the pipeline writes, parsed from the Record Process\n * `OutputMapping` JSON. Probes the common shapes (`field` / `targetField` /\n * `attribute`, or a `fields` array / object) and returns null otherwise. Never\n * throws on malformed JSON.\n */\n private static deriveOutputAttribute(outputMapping: string | null): string | null {\n if (!outputMapping) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(outputMapping);\n } catch {\n return null;\n }\n if (parsed == null || typeof parsed !== 'object') return null;\n const obj = parsed as Record<string, unknown>;\n\n const single = obj['field'] ?? obj['targetField'] ?? obj['attribute'];\n if (typeof single === 'string' && single.trim()) return single.trim();\n\n const names = FeaturePipelineEngine.fieldNamesFrom(obj['fields']);\n return names.length > 0 ? names.join(', ') : null;\n }\n\n /** Collect field names from either an object map or an array of names. */\n private static fieldNamesFrom(fields: unknown): string[] {\n if (Array.isArray(fields)) {\n return fields.filter((f): f is string => typeof f === 'string' && f.trim().length > 0);\n }\n if (fields != null && typeof fields === 'object') {\n return Object.keys(fields as Record<string, unknown>);\n }\n return [];\n }\n\n /**\n * Best-effort pipeline type derived from the Record Process `Configuration` JSON\n * (which serializes `DataFeatureSpec`). Returns 'LLM' when unset, empty, or unparseable, and a built-in\n * name ('decision') in its canonical case ('Decision').\n */\n private static derivePipelineType(config: string | null): string {\n const parsed = config ? SafeJSONParse<ConfigurationPipelineType>(config) : null;\n const typeName = parsed && typeof parsed === 'object' && typeof parsed.PipelineType === 'string' ? parsed.PipelineType.trim() : '';\n if (typeName.length === 0) {\n return 'LLM';\n }\n return BUILT_IN_PIPELINE_TYPES[typeName.toLowerCase()] ?? typeName;\n }\n}\n\n"]}
1
+ {"version":3,"file":"feature-pipeline.engine.js","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BaseEngine,\n BaseEnginePropertyConfig,\n IMetadataProvider,\n Metadata,\n UserInfo,\n} from '@memberjunction/core';\nimport { SafeJSONParse, UUIDsEqual } from '@memberjunction/global';\nimport { MJRecordProcessEntity, MJProcessRunEntity } from '@memberjunction/core-entities';\nimport { FeaturePipelineRunResults, type FeaturePipelineRunResult } from '@memberjunction/feature-pipelines';\nimport type { Observable } from 'rxjs';\n\n/**\n * Name of the seeded `MJ: Record Process Categories` row that marks a Record\n * Process as a Feature Pipeline. Authored in\n * `metadata/record-process-categories/.record-process-categories.json` and pushed\n * with `mj sync`. Mirrors `FEATURE_PIPELINE_CATEGORY_NAME` in the server-side\n * `@memberjunction/predictive-studio` engine — kept in sync by value (the two\n * packages don't share a dependency, so the constant is intentionally duplicated\n * rather than re-exported across the package boundary).\n */\nexport const FEATURE_PIPELINE_CATEGORY_NAME = 'Feature Pipeline';\n\n/** Normalized last-run status for a Feature Pipeline, derived from the most recent Process Run. */\nexport type FeaturePipelineRunStatus = 'Never' | 'Running' | 'Completed' | 'Failed' | 'Cancelled';\n\n/**\n * UI-facing summary of one Feature Pipeline: what it is, the entity it writes to,\n * the attribute it produces, and last-run / freshness. Built purely from the\n * cached Record Process + Process Run entities by {@link FeaturePipelineEngine.GetSummaries}.\n */\nexport interface FeaturePipelineSummary {\n /** The underlying `MJ: Record Processes` id (also the `RunFeaturePipeline` `featurePipelineID`). */\n ID: string;\n /** Display name (the Record Process name). */\n Name: string;\n /** Optional description. */\n Description: string | null;\n /** Record Process lifecycle status. */\n Status: 'Active' | 'Disabled' | 'Draft';\n /**\n * The per-record work the pipeline performs. Derived from the entity's `WorkType` so it tracks\n * the CodeGen-generated union (sourced from the `CK_RecordProcess_WorkType` CHECK constraint) and\n * never drifts as work types are added (e.g. `'ML Model'`).\n */\n WorkType: MJRecordProcessEntity['WorkType'];\n /** Id of the target entity whose records the pipeline derives features for. */\n TargetEntityID: string;\n /** Display name of the target entity. */\n TargetEntity: string;\n /** Best-effort attribute written back, parsed from the Record Process `OutputMapping`. */\n OutputAttribute: string | null;\n /** Whether the pipeline can be run on demand (the Run button is enabled). */\n OnDemandEnabled: boolean;\n /** Whether the pipeline runs on a cron schedule. */\n ScheduleEnabled: boolean;\n /** Timestamp of the most recent run (start time, falling back to end time), or null. */\n LastRunAt: Date | null;\n /** Normalized status of the most recent run. */\n LastRunStatus: FeaturePipelineRunStatus;\n /** Id of the most recent `MJ: Process Runs` row, or null. */\n LastRunProcessRunID: string | null;\n /** Records processed in the most recent run, or null. */\n LastRunProcessed: number | null;\n /** Records whose features were written in the most recent run, or null. */\n LastRunSuccess: number | null;\n /** Records that errored in the most recent run, or null. */\n LastRunErrors: number | null;\n /** What the most recent run did with its records ({@link FeaturePipelineEngine.DescribeRunOutcome}), or null for a run without a result. */\n LastRunOutcomeText: string | null;\n /** The most recent run's error message when it did not complete (a refused or failed run), else null. */\n LastRunErrorMessage: string | null;\n /**\n * The pipeline type ('LLM' or 'Decision' or custom). Defaults to 'LLM' when unset. The two built-in\n * names are given in their canonical case whatever case the spec uses, as the processor matches them.\n */\n PipelineType: string;\n}\n\n/** The one DataFeatureSpec property the summary reads from a Record Process `Configuration`. */\ninterface ConfigurationPipelineType {\n PipelineType?: string | null;\n}\n\n/** The built-in pipeline type names, by their lower-case form. */\nconst BUILT_IN_PIPELINE_TYPES: Record<string, string> = { llm: 'LLM', decision: 'Decision' };\n\n/**\n * **FeaturePipelineEngine** (Knowledge Hub, Angular) — the discovery + monitoring\n * cache for Feature Pipelines (Predictive Studio SP6).\n *\n * A Feature Pipeline has no dedicated entity: it IS an `MJ: Record Processes` row\n * categorized into the seeded {@link FEATURE_PIPELINE_CATEGORY_NAME} category. This\n * engine caches those rows (plus the recent `MJ: Process Runs` headers) so the KH\n * UI can list \"what feature pipelines exist, what entity each writes to, and when\n * each last ran\" — reactively, via the canonical MJ BaseEngine pattern (`Config()`\n * declaring `BaseEnginePropertyConfig[]`, lazy-loaded, `ObserveProperty`\n * reactivity for free).\n *\n * The freshness/projection logic is a pure static function ({@link BuildSummaries})\n * so it can be exercised without a provider, and it intentionally mirrors the\n * server-side `@memberjunction/predictive-studio` engine of the same name (the two\n * don't share a dependency).\n */\nexport class FeaturePipelineEngine extends BaseEngine<FeaturePipelineEngine> {\n /** Singleton accessor for the default provider — never `new` this directly. */\n public static get Instance(): FeaturePipelineEngine {\n return super.getInstance<FeaturePipelineEngine>();\n }\n\n private _recordProcesses: MJRecordProcessEntity[] = [];\n private _processRuns: MJProcessRunEntity[] = [];\n\n /**\n * Lazy-load the Record Process + recent Process Run caches. Safe to call from\n * every entry point — a no-op when already loaded (unless `forceRefresh`).\n * Process Runs are ordered newest-first so the most recent run per pipeline is\n * the first match.\n */\n public async Config(\n forceRefresh?: boolean,\n contextUser?: UserInfo,\n provider?: IMetadataProvider,\n ): Promise<void> {\n const c: Partial<BaseEnginePropertyConfig>[] = [\n { Type: 'entity', EntityName: 'MJ: Record Processes', PropertyName: '_recordProcesses', OrderBy: 'Name' },\n { Type: 'entity', EntityName: 'MJ: Process Runs', PropertyName: '_processRuns', OrderBy: '__mj_CreatedAt DESC' },\n ];\n await super.Load(c, provider ?? Metadata.Provider, forceRefresh, contextUser);\n }\n\n // ---- Cached arrays (sync getters; observe via the streams for reactive UI) ----\n\n /** Every cached Record Process categorized as a Feature Pipeline. */\n public get Pipelines(): MJRecordProcessEntity[] {\n return (this._recordProcesses ?? []).filter((rp) => FeaturePipelineEngine.isFeaturePipeline(rp));\n }\n\n /** All cached Process Run headers (newest first; all entities — callers filter). */\n public get Runs(): MJProcessRunEntity[] {\n return this._processRuns ?? [];\n }\n\n /** Reactive stream of the raw Record Processes — re-emits on any save/delete/remote-invalidate. */\n public get Pipelines$(): Observable<MJRecordProcessEntity[]> {\n return this.ObserveProperty<MJRecordProcessEntity>('_recordProcesses');\n }\n\n /** Reactive stream of the Process Run headers. */\n public get Runs$(): Observable<MJProcessRunEntity[]> {\n return this.ObserveProperty<MJProcessRunEntity>('_processRuns');\n }\n\n // ---- Projection ----\n\n /** Build the UI summaries for every feature pipeline from the loaded caches. */\n public GetSummaries(): FeaturePipelineSummary[] {\n return FeaturePipelineEngine.BuildSummaries(this.Pipelines, this.Runs);\n }\n\n /**\n * Pure, side-effect-free summary builder — used by {@link GetSummaries} and by\n * unit tests (which pass in-memory arrays, no provider).\n */\n public static BuildSummaries(\n pipelines: ReadonlyArray<MJRecordProcessEntity>,\n runs: ReadonlyArray<MJProcessRunEntity>,\n ): FeaturePipelineSummary[] {\n return pipelines.map((p) => FeaturePipelineEngine.buildSummary(p, runs));\n }\n\n // ---- Internal helpers ----\n\n /** Whether a Record Process belongs to the seeded Feature Pipeline category. */\n private static isFeaturePipeline(rp: MJRecordProcessEntity): boolean {\n return (rp.Category ?? '').trim().toLowerCase() === FEATURE_PIPELINE_CATEGORY_NAME.toLowerCase();\n }\n\n /** Project one pipeline + the run set into a {@link FeaturePipelineSummary}. */\n private static buildSummary(\n p: MJRecordProcessEntity,\n runs: ReadonlyArray<MJProcessRunEntity>,\n ): FeaturePipelineSummary {\n const latest = FeaturePipelineEngine.newestRun(runs, p.ID);\n const result = FeaturePipelineRunResults.Parse(latest?.ResultSummary);\n return {\n ID: p.ID,\n Name: p.Name,\n Description: p.Description ?? null,\n Status: p.Status,\n WorkType: p.WorkType,\n TargetEntityID: p.EntityID,\n TargetEntity: p.Entity,\n OutputAttribute: FeaturePipelineEngine.deriveOutputAttribute(p.OutputMapping),\n OnDemandEnabled: p.OnDemandEnabled,\n ScheduleEnabled: p.ScheduleEnabled,\n LastRunAt: latest?.StartTime ?? latest?.EndTime ?? null,\n LastRunStatus: FeaturePipelineEngine.mapRunStatus(latest),\n LastRunProcessRunID: latest?.ID ?? null,\n LastRunProcessed: latest?.ProcessedItems ?? null,\n LastRunSuccess: latest?.SuccessCount ?? null,\n LastRunErrors: latest?.ErrorCount ?? null,\n LastRunOutcomeText: latest && result ? FeaturePipelineEngine.DescribeRunOutcome(result, latest) : null,\n LastRunErrorMessage: latest && latest.Status !== 'Completed' ? latest.ErrorMessage || null : null,\n PipelineType: FeaturePipelineEngine.derivePipelineType(p.Configuration),\n };\n }\n\n /**\n * The card's last-run line from a run's result. Records skipped on their watermark count as unchanged: they still\n * hold what the pipeline wrote. A completed run with nothing to write is \"up to date\".\n */\n public static DescribeRunOutcome(result: FeaturePipelineRunResult, run: MJProcessRunEntity): string {\n const total = run.ProcessedItems || 0;\n const failed = run.ErrorCount || 0;\n const unchanged = result.Unchanged + result.Skipped;\n if (total === 0) {\n return 'no records processed';\n }\n if (run.Status === 'Completed' && result.Upserted === 0 && failed === 0) {\n return `${unchanged} of ${total} up to date (${unchanged} unchanged)`;\n }\n const parts: Array<[number, string]> = [[result.Upserted, 'written'], [unchanged, 'unchanged'], [failed, 'failed']];\n const shown = parts.filter(([count]) => count > 0).map(([count, label]) => `${count} ${label}`);\n return `${shown.length > 0 ? shown.join(', ') : '0 written'} of ${total}`;\n }\n\n /** Newest real (not dry) run for a pipeline id, scanning by StartTime/EndTime/createdAt regardless of input order. */\n private static newestRun(\n runs: ReadonlyArray<MJProcessRunEntity>,\n recordProcessID: string,\n ): MJProcessRunEntity | undefined {\n let best: MJProcessRunEntity | undefined;\n let bestTime = -Infinity;\n for (const r of runs) {\n if (r.RecordProcessID == null || !UUIDsEqual(r.RecordProcessID, recordProcessID) || r.DryRun) continue;\n const candidate = r.StartTime ?? r.EndTime ?? r.__mj_CreatedAt;\n const t = candidate ? new Date(candidate).getTime() : -Infinity;\n if (t > bestTime) {\n bestTime = t;\n best = r;\n }\n }\n return best;\n }\n\n /** Normalize a Process Run status into the small union the UI renders. `undefined` → `'Never'`. */\n private static mapRunStatus(run: MJProcessRunEntity | undefined): FeaturePipelineRunStatus {\n if (!run) return 'Never';\n switch (run.Status) {\n case 'Completed':\n return 'Completed';\n case 'Failed':\n return 'Failed';\n case 'Cancelled':\n return 'Cancelled';\n case 'Running':\n case 'Pending':\n case 'Paused':\n return 'Running';\n default:\n return 'Never';\n }\n }\n\n /**\n * Best-effort attribute the pipeline writes, parsed from the Record Process\n * `OutputMapping` JSON. Probes the common shapes (`field` / `targetField` /\n * `attribute`, or a `fields` array / object) and returns null otherwise. Never\n * throws on malformed JSON.\n */\n private static deriveOutputAttribute(outputMapping: string | null): string | null {\n if (!outputMapping) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(outputMapping);\n } catch {\n return null;\n }\n if (parsed == null || typeof parsed !== 'object') return null;\n const obj = parsed as Record<string, unknown>;\n\n const single = obj['field'] ?? obj['targetField'] ?? obj['attribute'];\n if (typeof single === 'string' && single.trim()) return single.trim();\n\n const names = FeaturePipelineEngine.fieldNamesFrom(obj['fields']);\n return names.length > 0 ? names.join(', ') : null;\n }\n\n /** Collect field names from either an object map or an array of names. */\n private static fieldNamesFrom(fields: unknown): string[] {\n if (Array.isArray(fields)) {\n return fields.filter((f): f is string => typeof f === 'string' && f.trim().length > 0);\n }\n if (fields != null && typeof fields === 'object') {\n return Object.keys(fields as Record<string, unknown>);\n }\n return [];\n }\n\n /**\n * Best-effort pipeline type derived from the Record Process `Configuration` JSON\n * (which serializes `DataFeatureSpec`). Returns 'LLM' when unset, empty, or unparseable, and a built-in\n * name ('decision') in its canonical case ('Decision').\n */\n private static derivePipelineType(config: string | null): string {\n const parsed = config ? SafeJSONParse<ConfigurationPipelineType>(config) : null;\n const typeName = parsed && typeof parsed === 'object' && typeof parsed.PipelineType === 'string' ? parsed.PipelineType.trim() : '';\n if (typeName.length === 0) {\n return 'LLM';\n }\n return BUILT_IN_PIPELINE_TYPES[typeName.toLowerCase()] ?? typeName;\n }\n}\n\n"]}
@@ -22,10 +22,18 @@
22
22
  */
23
23
  import { OnDestroy, AfterViewInit } from '@angular/core';
24
24
  import { Subject } from 'rxjs';
25
- import { ResourceData } from '@memberjunction/core-entities';
25
+ import { type RemoteOpResult } from '@memberjunction/core';
26
+ import { ResourceData, type PredictiveStudioRunFeaturePipelineOutput } from '@memberjunction/core-entities';
26
27
  import { BaseResourceComponent, NavigationService } from '@memberjunction/ng-shared';
27
28
  import { type FeaturePipelineSummary, type FeaturePipelineRunStatus } from './feature-pipeline.engine';
28
29
  import * as i0 from "@angular/core";
30
+ /** The toast a finished Run shows. */
31
+ export interface FeaturePipelineRunNotice {
32
+ Message: string;
33
+ Style: 'success' | 'warning' | 'error';
34
+ /** True only when the run's status is `Completed`. */
35
+ Completed: boolean;
36
+ }
29
37
  export declare class FeaturePipelinesResourceComponent extends BaseResourceComponent implements AfterViewInit, OnDestroy {
30
38
  private cdr;
31
39
  protected navigationService: NavigationService;
@@ -63,8 +71,9 @@ export declare class FeaturePipelinesResourceComponent extends BaseResourceCompo
63
71
  * `PredictiveStudio.RunFeaturePipeline` Remote Operation through the active
64
72
  * provider. The op delegates to the Record Set Processing substrate — the same
65
73
  * batching/resume/audit path the rest of the platform uses.
74
+ * @returns the notice the run showed, or null when a run of the pipeline was already in flight
66
75
  */
67
- OnRunPipeline(pipeline: FeaturePipelineSummary): Promise<void>;
76
+ OnRunPipeline(pipeline: FeaturePipelineSummary): Promise<FeaturePipelineRunNotice | null>;
68
77
  /** Whether a given pipeline currently has a Run in flight. */
69
78
  IsRunning(pipelineID: string): boolean;
70
79
  /** Font Awesome icon for a pipeline's work type. */
@@ -85,6 +94,11 @@ export declare class FeaturePipelinesResourceComponent extends BaseResourceCompo
85
94
  */
86
95
  private subscribeToEngine;
87
96
  private applyFilter;
97
+ /**
98
+ * The toast for a Run's result. The operation succeeding only means the run was carried out; a refused or failed
99
+ * run ends `Failed` with its error message, and a cancelled or paused run did not finish.
100
+ */
101
+ static RunResultNotice(pipelineName: string, result: RemoteOpResult<PredictiveStudioRunFeaturePipelineOutput>): FeaturePipelineRunNotice;
88
102
  private notifyRunResult;
89
103
  private emitAgentContext;
90
104
  /** Resolve a pipeline reference (id/name/contains) or return a tolerant failure. */
@@ -1 +1 @@
1
- {"version":3,"file":"feature-pipelines-resource.component.d.ts","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAGL,SAAS,EACT,aAAa,EAEd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAa,MAAM,MAAM,CAAC;AAE1C,OAAO,EACL,YAAY,EAIb,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAGrF,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;;AAWnC,qBAOa,iCACX,SAAQ,qBACR,YAAW,aAAa,EAAE,SAAS;IAEnC,OAAO,CAAC,GAAG,CAA6B;IACxC,UAAmB,iBAAiB,oBAA6B;IACjE,UAAmB,QAAQ,gBAAuB;IAM5C,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5D,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAQhE,mDAAmD;IAC5C,SAAS,UAAQ;IAExB,mDAAmD;IAC5C,YAAY,EAAE,sBAAsB,EAAE,CAAM;IAEnD,4EAA4E;IACrE,iBAAiB,EAAE,sBAAsB,EAAE,CAAM;IAExD,qEAAqE;IAC9D,WAAW,SAAM;IAExB,mFAAmF;IAC5E,UAAU,cAAqB;IAMtC,oEAAoE;IACpE,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED,8CAA8C;IAC9C,IAAW,aAAa,IAAI,MAAM,CAEjC;IAED,uDAAuD;IACvD,IAAW,WAAW,IAAI,MAAM,CAE/B;IAMK,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtC,WAAW,IAAI,IAAI;IAUnB,gDAAgD;IACnC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC,2DAA2D;IACpD,eAAe,IAAI,IAAI;IAM9B,kEAAkE;IAC3D,aAAa,IAAI,IAAI;IAQ5B,iFAAiF;IAC1E,cAAc,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAI7D;;;;;OAKG;IACU,aAAa,CAAC,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B3E,8DAA8D;IACvD,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAQ7C,oDAAoD;IAC7C,YAAY,CAAC,QAAQ,EAAE,sBAAsB,CAAC,UAAU,CAAC,GAAG,MAAM;IAkBzE,gDAAgD;IACzC,cAAc,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM;IAI/D,6CAA6C;IACtC,cAAc,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM;IAI/D,0DAA0D;IACnD,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM;IAgBzC,+EAA+E;IAC/E,OAAO,KAAK,MAAM,GAIjB;YAEa,QAAQ;IAkBtB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,eAAe;IAmCvB,OAAO,CAAC,gBAAgB;IAUxB,oFAAoF;IACpF,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,kBAAkB;yCA3Tf,iCAAiC;2CAAjC,iCAAiC;CA2X7C;AAED,mEAAmE;AACnE,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD"}
1
+ {"version":3,"file":"feature-pipelines-resource.component.d.ts","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
+ var FeaturePipelinesResourceComponent_1;
7
8
  /**
8
9
  * @fileoverview Knowledge Hub — Feature Pipelines dashboard tab.
9
10
  *
@@ -82,14 +83,34 @@ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_33_T
82
83
  } if (rf & 2) {
83
84
  const pipeline_r4 = i0.ɵɵnextContext().$implicit;
84
85
  i0.ɵɵadvance();
85
- i0.ɵɵtextInterpolate2("\u00B7 ", pipeline_r4.LastRunSuccess, "/", pipeline_r4.LastRunProcessed, " written");
86
+ i0.ɵɵtextInterpolate1("\u00B7 ", pipeline_r4.LastRunOutcomeText);
86
87
  } }
87
- function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_36_Template(rf, ctx) { if (rf & 1) {
88
- i0.ɵɵelement(0, "i", 40);
88
+ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_34_Template(rf, ctx) { if (rf & 1) {
89
+ i0.ɵɵelementStart(0, "span", 35);
90
+ i0.ɵɵtext(1);
91
+ i0.ɵɵelementEnd();
92
+ } if (rf & 2) {
93
+ const pipeline_r4 = i0.ɵɵnextContext().$implicit;
94
+ i0.ɵɵadvance();
95
+ i0.ɵɵtextInterpolate2("\u00B7 ", pipeline_r4.LastRunSuccess, "/", pipeline_r4.LastRunProcessed, " processed");
96
+ } }
97
+ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_35_Template(rf, ctx) { if (rf & 1) {
98
+ i0.ɵɵelementStart(0, "p", 36);
99
+ i0.ɵɵelement(1, "i", 41);
100
+ i0.ɵɵtext(2);
101
+ i0.ɵɵelementEnd();
102
+ } if (rf & 2) {
103
+ const pipeline_r4 = i0.ɵɵnextContext().$implicit;
104
+ i0.ɵɵproperty("title", pipeline_r4.LastRunErrorMessage);
105
+ i0.ɵɵadvance(2);
106
+ i0.ɵɵtextInterpolate1(" ", pipeline_r4.LastRunErrorMessage, " ");
107
+ } }
108
+ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_38_Template(rf, ctx) { if (rf & 1) {
109
+ i0.ɵɵelement(0, "i", 42);
89
110
  i0.ɵɵtext(1, " Running\u2026 ");
90
111
  } }
91
- function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_37_Template(rf, ctx) { if (rf & 1) {
92
- i0.ɵɵelement(0, "i", 41);
112
+ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_39_Template(rf, ctx) { if (rf & 1) {
113
+ i0.ɵɵelement(0, "i", 43);
93
114
  i0.ɵɵtext(1, " Run ");
94
115
  } }
95
116
  function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template(rf, ctx) { if (rf & 1) {
@@ -129,16 +150,18 @@ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template(rf, ctx
129
150
  i0.ɵɵelementEnd();
130
151
  i0.ɵɵelementStart(31, "span", 32);
131
152
  i0.ɵɵtext(32);
132
- i0.ɵɵconditionalCreate(33, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_33_Template, 2, 2, "span", 35);
133
- i0.ɵɵelementEnd()()();
134
- i0.ɵɵelementStart(34, "div", 36)(35, "button", 37);
135
- i0.ɵɵlistener("click", function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template_button_click_35_listener() { const pipeline_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnRunPipeline(pipeline_r4)); });
136
- i0.ɵɵconditionalCreate(36, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_36_Template, 2, 0)(37, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_37_Template, 2, 0);
153
+ i0.ɵɵconditionalCreate(33, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_33_Template, 2, 1, "span", 35)(34, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_34_Template, 2, 2, "span", 35);
154
+ i0.ɵɵelementEnd()();
155
+ i0.ɵɵconditionalCreate(35, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_35_Template, 3, 2, "p", 36);
156
+ i0.ɵɵelementEnd();
157
+ i0.ɵɵelementStart(36, "div", 37)(37, "button", 38);
158
+ i0.ɵɵlistener("click", function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template_button_click_37_listener() { const pipeline_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnRunPipeline(pipeline_r4)); });
159
+ i0.ɵɵconditionalCreate(38, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_38_Template, 2, 0)(39, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_39_Template, 2, 0);
137
160
  i0.ɵɵelementEnd();
138
- i0.ɵɵelementStart(38, "button", 38);
139
- i0.ɵɵlistener("click", function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template_button_click_38_listener() { const pipeline_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnViewPipeline(pipeline_r4)); });
140
- i0.ɵɵelement(39, "i", 39);
141
- i0.ɵɵtext(40, " View ");
161
+ i0.ɵɵelementStart(40, "button", 39);
162
+ i0.ɵɵlistener("click", function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template_button_click_40_listener() { const pipeline_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnViewPipeline(pipeline_r4)); });
163
+ i0.ɵɵelement(41, "i", 40);
164
+ i0.ɵɵtext(42, " View ");
142
165
  i0.ɵɵelementEnd()()();
143
166
  } if (rf & 2) {
144
167
  const pipeline_r4 = ctx.$implicit;
@@ -169,15 +192,17 @@ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template(rf, ctx
169
192
  i0.ɵɵadvance(6);
170
193
  i0.ɵɵtextInterpolate1(" ", ctx_r0.TimeAgo(pipeline_r4.LastRunAt), " ");
171
194
  i0.ɵɵadvance();
172
- i0.ɵɵconditional(pipeline_r4.LastRunProcessed != null ? 33 : -1);
195
+ i0.ɵɵconditional(pipeline_r4.LastRunOutcomeText ? 33 : pipeline_r4.LastRunProcessed != null ? 34 : -1);
196
+ i0.ɵɵadvance(2);
197
+ i0.ɵɵconditional(pipeline_r4.LastRunErrorMessage ? 35 : -1);
173
198
  i0.ɵɵadvance(2);
174
199
  i0.ɵɵproperty("disabled", !pipeline_r4.OnDemandEnabled || pipeline_r4.Status !== "Active" || ctx_r0.IsRunning(pipeline_r4.ID));
175
200
  i0.ɵɵadvance();
176
- i0.ɵɵconditional(ctx_r0.IsRunning(pipeline_r4.ID) ? 36 : 37);
201
+ i0.ɵɵconditional(ctx_r0.IsRunning(pipeline_r4.ID) ? 38 : 39);
177
202
  } }
178
203
  function FeaturePipelinesResourceComponent_Conditional_16_Template(rf, ctx) { if (rf & 1) {
179
204
  i0.ɵɵelementStart(0, "div", 14);
180
- i0.ɵɵrepeaterCreate(1, FeaturePipelinesResourceComponent_Conditional_16_For_2_Template, 41, 20, "div", 17, _forTrack0);
205
+ i0.ɵɵrepeaterCreate(1, FeaturePipelinesResourceComponent_Conditional_16_For_2_Template, 43, 21, "div", 17, _forTrack0);
181
206
  i0.ɵɵelementEnd();
182
207
  } if (rf & 2) {
183
208
  const ctx_r0 = i0.ɵɵnextContext();
@@ -187,6 +212,7 @@ function FeaturePipelinesResourceComponent_Conditional_16_Template(rf, ctx) { if
187
212
  /** Stable operation key of the Run Feature Pipeline Remote Operation. */
188
213
  const RUN_FEATURE_PIPELINE_OP = 'PredictiveStudio.RunFeaturePipeline';
189
214
  let FeaturePipelinesResourceComponent = class FeaturePipelinesResourceComponent extends BaseResourceComponent {
215
+ static { FeaturePipelinesResourceComponent_1 = this; }
190
216
  cdr = inject(ChangeDetectorRef);
191
217
  navigationService = inject(NavigationService);
192
218
  destroy$ = new Subject();
@@ -271,10 +297,11 @@ let FeaturePipelinesResourceComponent = class FeaturePipelinesResourceComponent
271
297
  * `PredictiveStudio.RunFeaturePipeline` Remote Operation through the active
272
298
  * provider. The op delegates to the Record Set Processing substrate — the same
273
299
  * batching/resume/audit path the rest of the platform uses.
300
+ * @returns the notice the run showed, or null when a run of the pipeline was already in flight
274
301
  */
275
302
  async OnRunPipeline(pipeline) {
276
303
  if (this.RunningIDs.has(pipeline.ID))
277
- return;
304
+ return null;
278
305
  this.RunningIDs.add(pipeline.ID);
279
306
  this.cdr.detectChanges();
280
307
  try {
@@ -284,10 +311,16 @@ let FeaturePipelinesResourceComponent = class FeaturePipelinesResourceComponent
284
311
  // The narrowing cast is the documented "power-tool seam" entry point.
285
312
  const opProvider = this.ProviderToUse;
286
313
  const result = await opProvider.RouteOperation(RUN_FEATURE_PIPELINE_OP, input, { provider: this.ProviderToUse });
287
- this.notifyRunResult(pipeline, result);
314
+ return this.notifyRunResult(pipeline, result);
288
315
  }
289
316
  catch (error) {
290
- MJNotificationService.Instance.CreateSimpleNotification(`Failed to run "${pipeline.Name}": ${error instanceof Error ? error.message : String(error)}`, 'error', 5000);
317
+ const notice = {
318
+ Style: 'error',
319
+ Completed: false,
320
+ Message: `Failed to run "${pipeline.Name}": ${error instanceof Error ? error.message : String(error)}`,
321
+ };
322
+ MJNotificationService.Instance.CreateSimpleNotification(notice.Message, notice.Style, 5000);
323
+ return notice;
291
324
  }
292
325
  finally {
293
326
  this.RunningIDs.delete(pipeline.ID);
@@ -393,14 +426,32 @@ let FeaturePipelinesResourceComponent = class FeaturePipelinesResourceComponent
393
426
  p.TargetEntity.toLowerCase().includes(q) ||
394
427
  (p.OutputAttribute ?? '').toLowerCase().includes(q));
395
428
  }
396
- notifyRunResult(pipeline, result) {
397
- if (result.Success && result.Output) {
398
- const out = result.Output;
399
- MJNotificationService.Instance.CreateSimpleNotification(`"${pipeline.Name}" finished: ${out.written} written, ${out.processed} processed, ${out.error} error(s).`, out.error > 0 ? 'warning' : 'success', 5000);
429
+ /**
430
+ * The toast for a Run's result. The operation succeeding only means the run was carried out; a refused or failed
431
+ * run ends `Failed` with its error message, and a cancelled or paused run did not finish.
432
+ */
433
+ static RunResultNotice(pipelineName, result) {
434
+ const out = result.Output;
435
+ if (!result.Success || !out) {
436
+ return { Style: 'error', Completed: false, Message: `"${pipelineName}" did not complete: ${result.ErrorMessage ?? 'unknown error'}` };
437
+ }
438
+ const reason = out.errorMessage || `the run ended with status ${out.status}`;
439
+ if (out.status === 'Failed') {
440
+ return { Style: 'error', Completed: false, Message: `"${pipelineName}" failed: ${reason}` };
400
441
  }
401
- else {
402
- MJNotificationService.Instance.CreateSimpleNotification(`"${pipeline.Name}" did not complete: ${result.ErrorMessage ?? 'unknown error'}`, 'error', 5000);
442
+ if (out.status !== 'Completed') {
443
+ return { Style: 'warning', Completed: false, Message: `"${pipelineName}" did not finish: ${reason}` };
403
444
  }
445
+ return {
446
+ Style: out.error > 0 ? 'warning' : 'success',
447
+ Completed: true,
448
+ Message: `"${pipelineName}" finished: ${out.processed} processed, ${out.written} succeeded, ${out.skipped} skipped unchanged, ${out.error} error(s).`,
449
+ };
450
+ }
451
+ notifyRunResult(pipeline, result) {
452
+ const notice = FeaturePipelinesResourceComponent_1.RunResultNotice(pipeline.Name, result);
453
+ MJNotificationService.Instance.CreateSimpleNotification(notice.Message, notice.Style, 5000);
454
+ return notice;
404
455
  }
405
456
  // ---- Agent context + client tools (required for every dashboard) ----
406
457
  //
@@ -459,8 +510,11 @@ let FeaturePipelinesResourceComponent = class FeaturePipelinesResourceComponent
459
510
  if (!r.pipeline.OnDemandEnabled) {
460
511
  return { Success: false, ErrorMessage: `"${r.pipeline.Name}" is not enabled for on-demand runs.` };
461
512
  }
462
- await this.OnRunPipeline(r.pipeline);
463
- return { Success: true, Data: { Pipeline: r.pipeline.Name } };
513
+ const notice = await this.OnRunPipeline(r.pipeline);
514
+ if (!notice?.Completed) {
515
+ return { Success: false, ErrorMessage: notice?.Message ?? `"${r.pipeline.Name}" is already running.` };
516
+ }
517
+ return { Success: true, Data: { Pipeline: r.pipeline.Name, Message: notice.Message } };
464
518
  },
465
519
  },
466
520
  {
@@ -509,7 +563,7 @@ let FeaturePipelinesResourceComponent = class FeaturePipelinesResourceComponent
509
563
  ]);
510
564
  }
511
565
  static ɵfac = /*@__PURE__*/ (() => { let ɵFeaturePipelinesResourceComponent_BaseFactory; return function FeaturePipelinesResourceComponent_Factory(__ngFactoryType__) { return (ɵFeaturePipelinesResourceComponent_BaseFactory || (ɵFeaturePipelinesResourceComponent_BaseFactory = i0.ɵɵgetInheritedFactory(FeaturePipelinesResourceComponent)))(__ngFactoryType__ || FeaturePipelinesResourceComponent); }; })();
512
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FeaturePipelinesResourceComponent, selectors: [["app-feature-pipelines-resource"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 17, vars: 5, consts: [["Title", "Feature Pipelines", "Icon", "fa-solid fa-diagram-project", "Subtitle", "Derive feature attributes from your data and persist them back to entities"], ["meta", ""], ["Label", "last run failed", "Variant", "error", 3, "Count"], ["Label", "pipelines", 3, "Count", "Total"], ["actions", ""], [3, "Clicked", "Loading"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-plus"], ["toolbar", ""], ["Placeholder", "Search by name, entity, or attribute\u2026", 3, "ValueChange", "Value"], [3, "Flex"], [1, "fp-loading"], ["Icon", "fa-solid fa-diagram-project", "Title", "No feature pipelines yet", "Message", "A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.", "ActionText", "New Pipeline", "ActionIcon", "fa-solid fa-plus"], ["Variant", "no-results", "Icon", "fa-solid fa-magnifying-glass", "Title", "No matching pipelines", "Message", "No feature pipelines match your search."], [1, "fp-grid"], ["text", "Loading feature pipelines\u2026", "size", "medium"], ["Icon", "fa-solid fa-diagram-project", "Title", "No feature pipelines yet", "Message", "A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.", "ActionText", "New Pipeline", "ActionIcon", "fa-solid fa-plus", 3, "Action"], [1, "fp-card", 3, "fp-card--disabled"], [1, "fp-card"], [1, "fp-card__head"], [1, "fp-card__icon"], [1, "fp-card__title"], [3, "title"], [1, "fp-card__subhead"], [1, "fp-card__worktype"], [1, "fp-type-badge"], [1, "fp-status"], [1, "fp-card__desc", 3, "title"], [1, "fp-card__meta"], [1, "fp-meta-row"], [1, "fp-meta-row__k"], [1, "fa-solid", "fa-table"], [1, "fp-meta-row__v"], [1, "fa-solid", "fa-pen-to-square"], [1, "fa-solid", "fa-clock-rotate-left"], [1, "fp-meta-row__sub"], [1, "fp-card__actions"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click", "disabled"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-eye"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-play"]], template: function FeaturePipelinesResourceComponent_Template(rf, ctx) { if (rf & 1) {
566
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FeaturePipelinesResourceComponent, selectors: [["app-feature-pipelines-resource"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 17, vars: 5, consts: [["Title", "Feature Pipelines", "Icon", "fa-solid fa-diagram-project", "Subtitle", "Derive feature attributes from your data and persist them back to entities"], ["meta", ""], ["Label", "last run failed", "Variant", "error", 3, "Count"], ["Label", "pipelines", 3, "Count", "Total"], ["actions", ""], [3, "Clicked", "Loading"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-plus"], ["toolbar", ""], ["Placeholder", "Search by name, entity, or attribute\u2026", 3, "ValueChange", "Value"], [3, "Flex"], [1, "fp-loading"], ["Icon", "fa-solid fa-diagram-project", "Title", "No feature pipelines yet", "Message", "A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.", "ActionText", "New Pipeline", "ActionIcon", "fa-solid fa-plus"], ["Variant", "no-results", "Icon", "fa-solid fa-magnifying-glass", "Title", "No matching pipelines", "Message", "No feature pipelines match your search."], [1, "fp-grid"], ["text", "Loading feature pipelines\u2026", "size", "medium"], ["Icon", "fa-solid fa-diagram-project", "Title", "No feature pipelines yet", "Message", "A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.", "ActionText", "New Pipeline", "ActionIcon", "fa-solid fa-plus", 3, "Action"], [1, "fp-card", 3, "fp-card--disabled"], [1, "fp-card"], [1, "fp-card__head"], [1, "fp-card__icon"], [1, "fp-card__title"], [3, "title"], [1, "fp-card__subhead"], [1, "fp-card__worktype"], [1, "fp-type-badge"], [1, "fp-status"], [1, "fp-card__desc", 3, "title"], [1, "fp-card__meta"], [1, "fp-meta-row"], [1, "fp-meta-row__k"], [1, "fa-solid", "fa-table"], [1, "fp-meta-row__v"], [1, "fa-solid", "fa-pen-to-square"], [1, "fa-solid", "fa-clock-rotate-left"], [1, "fp-meta-row__sub"], [1, "fp-card__error", 3, "title"], [1, "fp-card__actions"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click", "disabled"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-eye"], [1, "fa-solid", "fa-circle-exclamation"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-play"]], template: function FeaturePipelinesResourceComponent_Template(rf, ctx) { if (rf & 1) {
513
567
  i0.ɵɵelementStart(0, "mj-page-layout")(1, "mj-page-header", 0)(2, "div", 1);
514
568
  i0.ɵɵconditionalCreate(3, FeaturePipelinesResourceComponent_Conditional_3_Template, 1, 1, "mj-stat-badge", 2)(4, FeaturePipelinesResourceComponent_Conditional_4_Template, 1, 2, "mj-stat-badge", 3);
515
569
  i0.ɵɵelementEnd();
@@ -538,17 +592,17 @@ let FeaturePipelinesResourceComponent = class FeaturePipelinesResourceComponent
538
592
  i0.ɵɵproperty("Flex", true);
539
593
  i0.ɵɵadvance();
540
594
  i0.ɵɵconditional(ctx.IsLoading ? 13 : ctx.AllPipelines.length === 0 ? 14 : ctx.FilteredPipelines.length === 0 ? 15 : 16);
541
- } }, dependencies: [i1.LoadingComponent, i2.MJButtonDirective, i2.MJPageHeaderComponent, i2.MJPageLayoutComponent, i2.MJPageBodyComponent, i2.MJPageSearchComponent, i2.MJStatBadgeComponent, i2.MJRefreshButtonComponent, i2.MJEmptyStateComponent], styles: ["\n\n\n\n\n\n\n\n\n\n\n.fp-loading[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 240px;\n width: 100%;\n}\n\n.fp-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 16px;\n padding: 4px;\n align-content: start;\n}\n\n.fp-card[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg, 10px);\n background: var(--mj-bg-surface-card);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.fp-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.fp-card--disabled[_ngcontent-%COMP%] {\n opacity: 0.65;\n}\n\n.fp-card__head[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.fp-card__icon[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--mj-radius-md, 8px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.fp-card__title[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.fp-card__title[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-card__worktype[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n}\n\n.fp-card__subhead[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 2px;\n}\n\n.fp-type-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 1px 6px;\n border-radius: var(--mj-radius-sm, 4px);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.02em;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n}\n\n.fp-type-badge--decision[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n border-color: var(--mj-status-info-border);\n}\n\n.fp-card__desc[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--mj-text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.fp-card__meta[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 0;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.fp-meta-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n font-size: 13px;\n}\n\n.fp-meta-row__k[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n color: var(--mj-text-muted);\n}\n\n.fp-meta-row__k[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 14px;\n margin-right: 4px;\n}\n\n.fp-meta-row__v[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n text-align: right;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-meta-row__sub[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 12px;\n}\n\n.fp-card__actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-top: auto;\n}\n\n\n.fp-status[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n align-self: flex-start;\n padding: 2px 10px;\n border-radius: var(--mj-radius-full, 999px);\n font-size: 11px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.fp-status.status--never[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.fp-status.status--running[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.fp-status.status--completed[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.fp-status.status--failed[_ngcontent-%COMP%] {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.fp-status.status--cancelled[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}"] });
595
+ } }, dependencies: [i1.LoadingComponent, i2.MJButtonDirective, i2.MJPageHeaderComponent, i2.MJPageLayoutComponent, i2.MJPageBodyComponent, i2.MJPageSearchComponent, i2.MJStatBadgeComponent, i2.MJRefreshButtonComponent, i2.MJEmptyStateComponent], styles: ["\n\n\n\n\n\n\n\n\n\n\n.fp-loading[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 240px;\n width: 100%;\n}\n\n.fp-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 16px;\n padding: 4px;\n align-content: start;\n}\n\n.fp-card[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg, 10px);\n background: var(--mj-bg-surface-card);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.fp-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.fp-card--disabled[_ngcontent-%COMP%] {\n opacity: 0.65;\n}\n\n.fp-card__head[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.fp-card__icon[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--mj-radius-md, 8px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.fp-card__title[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.fp-card__title[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-card__worktype[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n}\n\n.fp-card__subhead[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 2px;\n}\n\n.fp-type-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 1px 6px;\n border-radius: var(--mj-radius-sm, 4px);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.02em;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n}\n\n.fp-type-badge--decision[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n border-color: var(--mj-status-info-border);\n}\n\n.fp-card__desc[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--mj-text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n\n.fp-card__error[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 12px;\n line-height: 1.4;\n color: var(--mj-status-error-text);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.fp-card__meta[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 0;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.fp-meta-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n font-size: 13px;\n}\n\n.fp-meta-row__k[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n color: var(--mj-text-muted);\n}\n\n.fp-meta-row__k[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 14px;\n margin-right: 4px;\n}\n\n.fp-meta-row__v[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n text-align: right;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-meta-row__sub[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 12px;\n}\n\n.fp-card__actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-top: auto;\n}\n\n\n.fp-status[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n align-self: flex-start;\n padding: 2px 10px;\n border-radius: var(--mj-radius-full, 999px);\n font-size: 11px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.fp-status.status--never[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.fp-status.status--running[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.fp-status.status--completed[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.fp-status.status--failed[_ngcontent-%COMP%] {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.fp-status.status--cancelled[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}"] });
542
596
  };
543
- FeaturePipelinesResourceComponent = __decorate([
597
+ FeaturePipelinesResourceComponent = FeaturePipelinesResourceComponent_1 = __decorate([
544
598
  RegisterClass(BaseResourceComponent, 'FeaturePipelinesResource')
545
599
  ], FeaturePipelinesResourceComponent);
546
600
  export { FeaturePipelinesResourceComponent };
547
601
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FeaturePipelinesResourceComponent, [{
548
602
  type: Component,
549
- args: [{ standalone: false, selector: 'app-feature-pipelines-resource', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Feature Pipelines\"\n Icon=\"fa-solid fa-diagram-project\"\n Subtitle=\"Derive feature attributes from your data and persist them back to entities\">\n <div meta>\n @if (FailedCount > 0) {\n <mj-stat-badge [Count]=\"FailedCount\" Label=\"last run failed\" Variant=\"error\"></mj-stat-badge>\n } @else {\n <mj-stat-badge\n [Count]=\"FilteredPipelines.length\"\n [Total]=\"AllPipelines.length\"\n Label=\"pipelines\">\n </mj-stat-badge>\n }\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"Refresh()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"OnNewPipeline()\">\n <i class=\"fa-solid fa-plus\"></i> New Pipeline\n </button>\n </div>\n <div toolbar>\n <mj-page-search\n [Value]=\"SearchQuery\"\n Placeholder=\"Search by name, entity, or attribute\u2026\"\n (ValueChange)=\"SearchQuery = $event; OnSearchChanged()\">\n </mj-page-search>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\">\n @if (IsLoading) {\n <div class=\"fp-loading\">\n <mj-loading text=\"Loading feature pipelines\u2026\" size=\"medium\"></mj-loading>\n </div>\n } @else if (AllPipelines.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-diagram-project\"\n Title=\"No feature pipelines yet\"\n Message=\"A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.\"\n ActionText=\"New Pipeline\"\n ActionIcon=\"fa-solid fa-plus\"\n (Action)=\"OnNewPipeline()\">\n </mj-empty-state>\n } @else if (FilteredPipelines.length === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Icon=\"fa-solid fa-magnifying-glass\"\n Title=\"No matching pipelines\"\n Message=\"No feature pipelines match your search.\">\n </mj-empty-state>\n } @else {\n <div class=\"fp-grid\">\n @for (pipeline of FilteredPipelines; track pipeline.ID) {\n <div class=\"fp-card\" [class.fp-card--disabled]=\"pipeline.Status !== 'Active'\">\n <div class=\"fp-card__head\">\n <i class=\"fp-card__icon\" [class]=\"WorkTypeIcon(pipeline.WorkType)\"></i>\n <div class=\"fp-card__title\">\n <h3 [title]=\"pipeline.Name\">{{ pipeline.Name }}</h3>\n <div class=\"fp-card__subhead\">\n <span class=\"fp-card__worktype\">{{ pipeline.WorkType }}</span>\n <span class=\"fp-type-badge\" [class.fp-type-badge--decision]=\"pipeline.PipelineType === 'Decision'\">{{ pipeline.PipelineType }}</span>\n </div>\n </div>\n <span class=\"fp-status\" [class]=\"RunStatusClass(pipeline.LastRunStatus)\">\n {{ RunStatusLabel(pipeline.LastRunStatus) }}\n </span>\n </div>\n\n @if (pipeline.Description) {\n <p class=\"fp-card__desc\" [title]=\"pipeline.Description\">{{ pipeline.Description }}</p>\n }\n\n <div class=\"fp-card__meta\">\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-table\"></i> Target</span>\n <span class=\"fp-meta-row__v\">{{ pipeline.TargetEntity }}</span>\n </div>\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-pen-to-square\"></i> Writes</span>\n <span class=\"fp-meta-row__v\">{{ pipeline.OutputAttribute || '\u2014' }}</span>\n </div>\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-clock-rotate-left\"></i> Last run</span>\n <span class=\"fp-meta-row__v\">\n {{ TimeAgo(pipeline.LastRunAt) }}\n @if (pipeline.LastRunProcessed != null) {\n <span class=\"fp-meta-row__sub\">\u00B7 {{ pipeline.LastRunSuccess }}/{{ pipeline.LastRunProcessed }} written</span>\n }\n </span>\n </div>\n </div>\n\n <div class=\"fp-card__actions\">\n <button\n mjButton\n variant=\"primary\"\n size=\"sm\"\n [disabled]=\"!pipeline.OnDemandEnabled || pipeline.Status !== 'Active' || IsRunning(pipeline.ID)\"\n (click)=\"OnRunPipeline(pipeline)\">\n @if (IsRunning(pipeline.ID)) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Running\u2026\n } @else {\n <i class=\"fa-solid fa-play\"></i> Run\n }\n </button>\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"OnViewPipeline(pipeline)\">\n <i class=\"fa-solid fa-eye\"></i> View\n </button>\n </div>\n </div>\n }\n </div>\n }\n </mj-page-body>\n</mj-page-layout>\n", styles: ["/**\n * Feature Pipelines (Knowledge Hub) \u2014 card-grid layout.\n * All colors use semantic design tokens (dark-mode + white-label safe).\n *\n * NOTE: this package builds with bare `ngc` (no Sass step) \u2014 component styles\n * are embedded RAW into the compiled JS. Keep this file FLAT, plain CSS.\n * Sass nesting / BEM selector concatenation would be silently dropped by the\n * browser and the rules would be DEAD. See .github/scripts/check-ngc-scss.sh\n * (CI gate) and MemberJunction/MJ#3105.\n */\n\n.fp-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 240px;\n width: 100%;\n}\n\n.fp-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 16px;\n padding: 4px;\n align-content: start;\n}\n\n.fp-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg, 10px);\n background: var(--mj-bg-surface-card);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.fp-card:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.fp-card--disabled {\n opacity: 0.65;\n}\n\n.fp-card__head {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.fp-card__icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--mj-radius-md, 8px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.fp-card__title {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.fp-card__title h3 {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-card__worktype {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n}\n\n.fp-card__subhead {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 2px;\n}\n\n.fp-type-badge {\n display: inline-flex;\n align-items: center;\n padding: 1px 6px;\n border-radius: var(--mj-radius-sm, 4px);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.02em;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n}\n\n.fp-type-badge--decision {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n border-color: var(--mj-status-info-border);\n}\n\n.fp-card__desc {\n margin: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--mj-text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.fp-card__meta {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 0;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.fp-meta-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n font-size: 13px;\n}\n\n.fp-meta-row__k {\n flex: 0 0 auto;\n color: var(--mj-text-muted);\n}\n\n.fp-meta-row__k i {\n width: 14px;\n margin-right: 4px;\n}\n\n.fp-meta-row__v {\n flex: 1 1 auto;\n text-align: right;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-meta-row__sub {\n color: var(--mj-text-muted);\n font-size: 12px;\n}\n\n.fp-card__actions {\n display: flex;\n gap: 8px;\n margin-top: auto;\n}\n\n/* Run-status pill \u2014 semantic status tokens */\n.fp-status {\n flex: 0 0 auto;\n align-self: flex-start;\n padding: 2px 10px;\n border-radius: var(--mj-radius-full, 999px);\n font-size: 11px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.fp-status.status--never {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.fp-status.status--running {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.fp-status.status--completed {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.fp-status.status--failed {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.fp-status.status--cancelled {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n"] }]
603
+ args: [{ standalone: false, selector: 'app-feature-pipelines-resource', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Feature Pipelines\"\n Icon=\"fa-solid fa-diagram-project\"\n Subtitle=\"Derive feature attributes from your data and persist them back to entities\">\n <div meta>\n @if (FailedCount > 0) {\n <mj-stat-badge [Count]=\"FailedCount\" Label=\"last run failed\" Variant=\"error\"></mj-stat-badge>\n } @else {\n <mj-stat-badge\n [Count]=\"FilteredPipelines.length\"\n [Total]=\"AllPipelines.length\"\n Label=\"pipelines\">\n </mj-stat-badge>\n }\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"Refresh()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"OnNewPipeline()\">\n <i class=\"fa-solid fa-plus\"></i> New Pipeline\n </button>\n </div>\n <div toolbar>\n <mj-page-search\n [Value]=\"SearchQuery\"\n Placeholder=\"Search by name, entity, or attribute\u2026\"\n (ValueChange)=\"SearchQuery = $event; OnSearchChanged()\">\n </mj-page-search>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\">\n @if (IsLoading) {\n <div class=\"fp-loading\">\n <mj-loading text=\"Loading feature pipelines\u2026\" size=\"medium\"></mj-loading>\n </div>\n } @else if (AllPipelines.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-diagram-project\"\n Title=\"No feature pipelines yet\"\n Message=\"A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.\"\n ActionText=\"New Pipeline\"\n ActionIcon=\"fa-solid fa-plus\"\n (Action)=\"OnNewPipeline()\">\n </mj-empty-state>\n } @else if (FilteredPipelines.length === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Icon=\"fa-solid fa-magnifying-glass\"\n Title=\"No matching pipelines\"\n Message=\"No feature pipelines match your search.\">\n </mj-empty-state>\n } @else {\n <div class=\"fp-grid\">\n @for (pipeline of FilteredPipelines; track pipeline.ID) {\n <div class=\"fp-card\" [class.fp-card--disabled]=\"pipeline.Status !== 'Active'\">\n <div class=\"fp-card__head\">\n <i class=\"fp-card__icon\" [class]=\"WorkTypeIcon(pipeline.WorkType)\"></i>\n <div class=\"fp-card__title\">\n <h3 [title]=\"pipeline.Name\">{{ pipeline.Name }}</h3>\n <div class=\"fp-card__subhead\">\n <span class=\"fp-card__worktype\">{{ pipeline.WorkType }}</span>\n <span class=\"fp-type-badge\" [class.fp-type-badge--decision]=\"pipeline.PipelineType === 'Decision'\">{{ pipeline.PipelineType }}</span>\n </div>\n </div>\n <span class=\"fp-status\" [class]=\"RunStatusClass(pipeline.LastRunStatus)\">\n {{ RunStatusLabel(pipeline.LastRunStatus) }}\n </span>\n </div>\n\n @if (pipeline.Description) {\n <p class=\"fp-card__desc\" [title]=\"pipeline.Description\">{{ pipeline.Description }}</p>\n }\n\n <div class=\"fp-card__meta\">\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-table\"></i> Target</span>\n <span class=\"fp-meta-row__v\">{{ pipeline.TargetEntity }}</span>\n </div>\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-pen-to-square\"></i> Writes</span>\n <span class=\"fp-meta-row__v\">{{ pipeline.OutputAttribute || '\u2014' }}</span>\n </div>\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-clock-rotate-left\"></i> Last run</span>\n <span class=\"fp-meta-row__v\">\n {{ TimeAgo(pipeline.LastRunAt) }}\n @if (pipeline.LastRunOutcomeText) {\n <span class=\"fp-meta-row__sub\">\u00B7 {{ pipeline.LastRunOutcomeText }}</span>\n } @else if (pipeline.LastRunProcessed != null) {\n <span class=\"fp-meta-row__sub\">\u00B7 {{ pipeline.LastRunSuccess }}/{{ pipeline.LastRunProcessed }} processed</span>\n }\n </span>\n </div>\n @if (pipeline.LastRunErrorMessage) {\n <p class=\"fp-card__error\" [title]=\"pipeline.LastRunErrorMessage\">\n <i class=\"fa-solid fa-circle-exclamation\"></i> {{ pipeline.LastRunErrorMessage }}\n </p>\n }\n </div>\n\n <div class=\"fp-card__actions\">\n <button\n mjButton\n variant=\"primary\"\n size=\"sm\"\n [disabled]=\"!pipeline.OnDemandEnabled || pipeline.Status !== 'Active' || IsRunning(pipeline.ID)\"\n (click)=\"OnRunPipeline(pipeline)\">\n @if (IsRunning(pipeline.ID)) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Running\u2026\n } @else {\n <i class=\"fa-solid fa-play\"></i> Run\n }\n </button>\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"OnViewPipeline(pipeline)\">\n <i class=\"fa-solid fa-eye\"></i> View\n </button>\n </div>\n </div>\n }\n </div>\n }\n </mj-page-body>\n</mj-page-layout>\n", styles: ["/**\n * Feature Pipelines (Knowledge Hub) \u2014 card-grid layout.\n * All colors use semantic design tokens (dark-mode + white-label safe).\n *\n * NOTE: this package builds with bare `ngc` (no Sass step) \u2014 component styles\n * are embedded RAW into the compiled JS. Keep this file FLAT, plain CSS.\n * Sass nesting / BEM selector concatenation would be silently dropped by the\n * browser and the rules would be DEAD. See .github/scripts/check-ngc-scss.sh\n * (CI gate) and MemberJunction/MJ#3105.\n */\n\n.fp-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 240px;\n width: 100%;\n}\n\n.fp-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 16px;\n padding: 4px;\n align-content: start;\n}\n\n.fp-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg, 10px);\n background: var(--mj-bg-surface-card);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.fp-card:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.fp-card--disabled {\n opacity: 0.65;\n}\n\n.fp-card__head {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.fp-card__icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--mj-radius-md, 8px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.fp-card__title {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.fp-card__title h3 {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-card__worktype {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n}\n\n.fp-card__subhead {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 2px;\n}\n\n.fp-type-badge {\n display: inline-flex;\n align-items: center;\n padding: 1px 6px;\n border-radius: var(--mj-radius-sm, 4px);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.02em;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n}\n\n.fp-type-badge--decision {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n border-color: var(--mj-status-info-border);\n}\n\n.fp-card__desc {\n margin: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--mj-text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* A failed or refused last run's reason: two lines, the full text on hover */\n.fp-card__error {\n margin: 0;\n font-size: 12px;\n line-height: 1.4;\n color: var(--mj-status-error-text);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.fp-card__meta {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 0;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.fp-meta-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n font-size: 13px;\n}\n\n.fp-meta-row__k {\n flex: 0 0 auto;\n color: var(--mj-text-muted);\n}\n\n.fp-meta-row__k i {\n width: 14px;\n margin-right: 4px;\n}\n\n.fp-meta-row__v {\n flex: 1 1 auto;\n text-align: right;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-meta-row__sub {\n color: var(--mj-text-muted);\n font-size: 12px;\n}\n\n.fp-card__actions {\n display: flex;\n gap: 8px;\n margin-top: auto;\n}\n\n/* Run-status pill \u2014 semantic status tokens */\n.fp-status {\n flex: 0 0 auto;\n align-self: flex-start;\n padding: 2px 10px;\n border-radius: var(--mj-radius-full, 999px);\n font-size: 11px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.fp-status.status--never {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.fp-status.status--running {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.fp-status.status--completed {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.fp-status.status--failed {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.fp-status.status--cancelled {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n"] }]
550
604
  }], null, null); })();
551
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FeaturePipelinesResourceComponent, { className: "FeaturePipelinesResourceComponent", filePath: "src/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.ts", lineNumber: 65 }); })();
605
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FeaturePipelinesResourceComponent, { className: "FeaturePipelinesResourceComponent", filePath: "src/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.ts", lineNumber: 73 }); })();
552
606
  /** Tree-shaking prevention — keeps the component in the bundle. */
553
607
  export function LoadFeaturePipelinesResource() {
554
608
  // Prevents tree-shaking of the component.