@memberjunction/ng-record-process-studio 0.0.1 → 5.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,304 @@
1
+ /**
2
+ * @fileoverview The Bulk Operations hub: lists the org's Record Processes, lets a user create / edit one
3
+ * (via the embedded {@link RecordProcessEditorComponent}), and run any of them on demand (mounting the
4
+ * same dry-run → confirm runner used from a grid). Generic + reusable — Explorer's Bulk Operations app
5
+ * hosts it, but any MJ app can drop it in.
6
+ * @module @memberjunction/ng-record-process-studio
7
+ */
8
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, inject } from '@angular/core';
9
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
10
+ import { RunView } from '@memberjunction/core';
11
+ import { EntityActionUXHostComponent } from '@memberjunction/ng-entity-action-ux';
12
+ import { MJButtonDirective } from '@memberjunction/ng-ui-components';
13
+ import { RecordProcessEditorComponent } from '../record-process-editor/record-process-editor.component';
14
+ import * as i0 from "@angular/core";
15
+ const _forTrack0 = ($index, $item) => $item.ID;
16
+ function RecordProcessStudioComponent_Conditional_0_Conditional_11_Template(rf, ctx) { if (rf & 1) {
17
+ i0.ɵɵelementStart(0, "div", 10);
18
+ i0.ɵɵelement(1, "i", 13);
19
+ i0.ɵɵtext(2, " Loading\u2026");
20
+ i0.ɵɵelementEnd();
21
+ } }
22
+ function RecordProcessStudioComponent_Conditional_0_Conditional_12_Template(rf, ctx) { if (rf & 1) {
23
+ const _r3 = i0.ɵɵgetCurrentView();
24
+ i0.ɵɵelementStart(0, "div", 11);
25
+ i0.ɵɵelement(1, "i", 14);
26
+ i0.ɵɵelementStart(2, "p");
27
+ i0.ɵɵtext(3, "No bulk operations yet.");
28
+ i0.ɵɵelementEnd();
29
+ i0.ɵɵelementStart(4, "button", 8);
30
+ i0.ɵɵlistener("click", function RecordProcessStudioComponent_Conditional_0_Conditional_12_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.New()); });
31
+ i0.ɵɵelement(5, "i", 9);
32
+ i0.ɵɵtext(6, " Create one");
33
+ i0.ɵɵelementEnd()();
34
+ } }
35
+ function RecordProcessStudioComponent_Conditional_0_Conditional_13_For_13_Conditional_4_Template(rf, ctx) { if (rf & 1) {
36
+ i0.ɵɵelementStart(0, "div", 20);
37
+ i0.ɵɵtext(1);
38
+ i0.ɵɵelementEnd();
39
+ } if (rf & 2) {
40
+ const p_r5 = i0.ɵɵnextContext().$implicit;
41
+ i0.ɵɵadvance();
42
+ i0.ɵɵtextInterpolate(p_r5.Description);
43
+ } }
44
+ function RecordProcessStudioComponent_Conditional_0_Conditional_13_For_13_Template(rf, ctx) { if (rf & 1) {
45
+ const _r4 = i0.ɵɵgetCurrentView();
46
+ i0.ɵɵelementStart(0, "div", 18);
47
+ i0.ɵɵlistener("click", function RecordProcessStudioComponent_Conditional_0_Conditional_13_For_13_Template_div_click_0_listener() { const p_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.Edit(p_r5)); });
48
+ i0.ɵɵelementStart(1, "div")(2, "div", 19);
49
+ i0.ɵɵtext(3);
50
+ i0.ɵɵelementEnd();
51
+ i0.ɵɵconditionalCreate(4, RecordProcessStudioComponent_Conditional_0_Conditional_13_For_13_Conditional_4_Template, 2, 1, "div", 20);
52
+ i0.ɵɵelementEnd();
53
+ i0.ɵɵelementStart(5, "div");
54
+ i0.ɵɵtext(6);
55
+ i0.ɵɵelementEnd();
56
+ i0.ɵɵelementStart(7, "div")(8, "span", 21);
57
+ i0.ɵɵtext(9);
58
+ i0.ɵɵelementEnd()();
59
+ i0.ɵɵelementStart(10, "div")(11, "span", 22);
60
+ i0.ɵɵtext(12);
61
+ i0.ɵɵelementEnd()();
62
+ i0.ɵɵelementStart(13, "div", 23);
63
+ i0.ɵɵlistener("click", function RecordProcessStudioComponent_Conditional_0_Conditional_13_For_13_Template_div_click_13_listener($event) { i0.ɵɵrestoreView(_r4); return i0.ɵɵresetView($event.stopPropagation()); });
64
+ i0.ɵɵelementStart(14, "button", 24);
65
+ i0.ɵɵlistener("click", function RecordProcessStudioComponent_Conditional_0_Conditional_13_For_13_Template_button_click_14_listener() { const p_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.Run(p_r5)); });
66
+ i0.ɵɵelement(15, "i", 25);
67
+ i0.ɵɵelementEnd();
68
+ i0.ɵɵelementStart(16, "button", 26);
69
+ i0.ɵɵlistener("click", function RecordProcessStudioComponent_Conditional_0_Conditional_13_For_13_Template_button_click_16_listener() { const p_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.Edit(p_r5)); });
70
+ i0.ɵɵelement(17, "i", 27);
71
+ i0.ɵɵelementEnd()()();
72
+ } if (rf & 2) {
73
+ const p_r5 = ctx.$implicit;
74
+ const ctx_r1 = i0.ɵɵnextContext(3);
75
+ i0.ɵɵadvance(3);
76
+ i0.ɵɵtextInterpolate(p_r5.Name);
77
+ i0.ɵɵadvance();
78
+ i0.ɵɵconditional(p_r5.Description ? 4 : -1);
79
+ i0.ɵɵadvance(2);
80
+ i0.ɵɵtextInterpolate(p_r5.Entity);
81
+ i0.ɵɵadvance(3);
82
+ i0.ɵɵtextInterpolate(ctx_r1.workLabel(p_r5.WorkType));
83
+ i0.ɵɵadvance(2);
84
+ i0.ɵɵclassProp("ok", p_r5.Status === "Active")("warn", p_r5.Status === "Draft")("muted", p_r5.Status === "Disabled");
85
+ i0.ɵɵadvance();
86
+ i0.ɵɵtextInterpolate(p_r5.Status);
87
+ } }
88
+ function RecordProcessStudioComponent_Conditional_0_Conditional_13_Template(rf, ctx) { if (rf & 1) {
89
+ i0.ɵɵelementStart(0, "div", 12)(1, "div", 15)(2, "div");
90
+ i0.ɵɵtext(3, "Operation");
91
+ i0.ɵɵelementEnd();
92
+ i0.ɵɵelementStart(4, "div");
93
+ i0.ɵɵtext(5, "Entity");
94
+ i0.ɵɵelementEnd();
95
+ i0.ɵɵelementStart(6, "div");
96
+ i0.ɵɵtext(7, "Type");
97
+ i0.ɵɵelementEnd();
98
+ i0.ɵɵelementStart(8, "div");
99
+ i0.ɵɵtext(9, "Status");
100
+ i0.ɵɵelementEnd();
101
+ i0.ɵɵelementStart(10, "div", 16);
102
+ i0.ɵɵtext(11, "Actions");
103
+ i0.ɵɵelementEnd()();
104
+ i0.ɵɵrepeaterCreate(12, RecordProcessStudioComponent_Conditional_0_Conditional_13_For_13_Template, 18, 11, "div", 17, _forTrack0);
105
+ i0.ɵɵelementEnd();
106
+ } if (rf & 2) {
107
+ const ctx_r1 = i0.ɵɵnextContext(2);
108
+ i0.ɵɵadvance(12);
109
+ i0.ɵɵrepeater(ctx_r1.Filtered);
110
+ } }
111
+ function RecordProcessStudioComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
112
+ const _r1 = i0.ɵɵgetCurrentView();
113
+ i0.ɵɵelementStart(0, "div", 1)(1, "div", 2);
114
+ i0.ɵɵelement(2, "i", 3);
115
+ i0.ɵɵelementStart(3, "input", 4);
116
+ i0.ɵɵlistener("input", function RecordProcessStudioComponent_Conditional_0_Template_input_input_3_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSearch($event)); });
117
+ i0.ɵɵelementEnd()();
118
+ i0.ɵɵelement(4, "span", 5);
119
+ i0.ɵɵelementStart(5, "button", 6);
120
+ i0.ɵɵlistener("click", function RecordProcessStudioComponent_Conditional_0_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.reload()); });
121
+ i0.ɵɵelement(6, "i", 7);
122
+ i0.ɵɵtext(7, " Refresh");
123
+ i0.ɵɵelementEnd();
124
+ i0.ɵɵelementStart(8, "button", 8);
125
+ i0.ɵɵlistener("click", function RecordProcessStudioComponent_Conditional_0_Template_button_click_8_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.New()); });
126
+ i0.ɵɵelement(9, "i", 9);
127
+ i0.ɵɵtext(10, " New operation");
128
+ i0.ɵɵelementEnd()();
129
+ i0.ɵɵconditionalCreate(11, RecordProcessStudioComponent_Conditional_0_Conditional_11_Template, 3, 0, "div", 10)(12, RecordProcessStudioComponent_Conditional_0_Conditional_12_Template, 7, 0, "div", 11)(13, RecordProcessStudioComponent_Conditional_0_Conditional_13_Template, 14, 0, "div", 12);
130
+ } if (rf & 2) {
131
+ const ctx_r1 = i0.ɵɵnextContext();
132
+ i0.ɵɵadvance(3);
133
+ i0.ɵɵproperty("value", ctx_r1.Search);
134
+ i0.ɵɵadvance(8);
135
+ i0.ɵɵconditional(ctx_r1.Loading ? 11 : ctx_r1.Filtered.length === 0 ? 12 : 13);
136
+ } }
137
+ function RecordProcessStudioComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
138
+ const _r6 = i0.ɵɵgetCurrentView();
139
+ i0.ɵɵelementStart(0, "div", 28)(1, "button", 6);
140
+ i0.ɵɵlistener("click", function RecordProcessStudioComponent_Conditional_1_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.backToList()); });
141
+ i0.ɵɵelement(2, "i", 29);
142
+ i0.ɵɵtext(3, " Back");
143
+ i0.ɵɵelementEnd();
144
+ i0.ɵɵelementStart(4, "h2");
145
+ i0.ɵɵtext(5);
146
+ i0.ɵɵelementEnd()();
147
+ i0.ɵɵelementStart(6, "mj-record-process-editor", 30);
148
+ i0.ɵɵlistener("Saved", function RecordProcessStudioComponent_Conditional_1_Template_mj_record_process_editor_Saved_6_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSaved()); })("Cancelled", function RecordProcessStudioComponent_Conditional_1_Template_mj_record_process_editor_Cancelled_6_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.backToList()); });
149
+ i0.ɵɵelementEnd();
150
+ } if (rf & 2) {
151
+ const ctx_r1 = i0.ɵɵnextContext();
152
+ i0.ɵɵadvance(5);
153
+ i0.ɵɵtextInterpolate(ctx_r1.EditingID ? "Edit operation" : "New operation");
154
+ i0.ɵɵadvance();
155
+ i0.ɵɵproperty("RecordProcessID", ctx_r1.EditingID)("DefaultEntityID", ctx_r1.EntityID)("Provider", ctx_r1.Provider);
156
+ } }
157
+ function RecordProcessStudioComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
158
+ const _r7 = i0.ɵɵgetCurrentView();
159
+ i0.ɵɵelementStart(0, "mj-entity-action-ux-host", 31);
160
+ i0.ɵɵlistener("Completed", function RecordProcessStudioComponent_Conditional_2_Template_mj_entity_action_ux_host_Completed_0_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onRunDone($event)); })("Cancelled", function RecordProcessStudioComponent_Conditional_2_Template_mj_entity_action_ux_host_Cancelled_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.RunDriver = null); });
161
+ i0.ɵɵelementEnd();
162
+ } if (rf & 2) {
163
+ const ctx_r1 = i0.ɵɵnextContext();
164
+ i0.ɵɵproperty("DriverClass", ctx_r1.RunDriver.DriverClass)("Context", ctx_r1.RunDriver.Context);
165
+ } }
166
+ export class RecordProcessStudioComponent extends BaseAngularComponent {
167
+ cdr = inject(ChangeDetectorRef);
168
+ /** Optional: restrict the hub to processes for one entity (e.g. when embedded on an entity page). */
169
+ EntityID = null;
170
+ Mode = 'list';
171
+ EditingID = null;
172
+ Loading = true;
173
+ Search = '';
174
+ Processes = [];
175
+ RunDriver = null;
176
+ async ngOnInit() {
177
+ await this.reload();
178
+ }
179
+ get Filtered() {
180
+ const q = this.Search.trim().toLowerCase();
181
+ if (!q)
182
+ return this.Processes;
183
+ return this.Processes.filter((p) => p.Name.toLowerCase().includes(q) || (p.Entity ?? '').toLowerCase().includes(q));
184
+ }
185
+ async reload() {
186
+ this.Loading = true;
187
+ this.cdr.detectChanges();
188
+ const rv = this.Provider ? RunView.FromMetadataProvider(this.Provider) : new RunView();
189
+ const filter = this.EntityID ? `EntityID='${this.EntityID}'` : '';
190
+ const result = await rv.RunView({
191
+ EntityName: 'MJ: Record Processes',
192
+ Fields: ['ID', 'Name', 'Description', 'Entity', 'EntityID', 'WorkType', 'Status', 'ScopeType', 'ScopeFilter', 'ScopeViewID', 'ScopeListID'],
193
+ ExtraFilter: filter,
194
+ OrderBy: 'Name',
195
+ ResultType: 'simple',
196
+ });
197
+ this.Processes = result.Success ? (result.Results ?? []) : [];
198
+ this.Loading = false;
199
+ this.cdr.detectChanges();
200
+ }
201
+ onSearch(event) { this.Search = event.target.value; this.cdr.detectChanges(); }
202
+ New() { this.EditingID = null; this.Mode = 'edit'; this.cdr.detectChanges(); }
203
+ Edit(p) { this.EditingID = p.ID; this.Mode = 'edit'; this.cdr.detectChanges(); }
204
+ backToList() { this.Mode = 'list'; this.EditingID = null; this.cdr.detectChanges(); }
205
+ async onSaved() { this.backToList(); await this.reload(); }
206
+ /** Runs a process on demand against its default scope, via the dry-run → confirm runner. */
207
+ Run(p) {
208
+ const entity = this.ProviderToUse.EntityByID(p.EntityID);
209
+ if (!entity)
210
+ return;
211
+ this.RunDriver = {
212
+ DriverClass: 'RecordProcessRunnerUX',
213
+ Context: {
214
+ EntityInfo: entity,
215
+ ScopeKind: p.ScopeType?.toLowerCase() || 'filter',
216
+ Filter: p.ScopeFilter ?? undefined,
217
+ ViewID: p.ScopeViewID ?? undefined,
218
+ ListID: p.ScopeListID ?? undefined,
219
+ Config: { RecordProcessID: p.ID },
220
+ Provider: this.ProviderToUse,
221
+ ActionLabel: p.Name,
222
+ },
223
+ };
224
+ this.cdr.detectChanges();
225
+ }
226
+ onRunDone(_r) { this.RunDriver = null; this.cdr.detectChanges(); }
227
+ workLabel(workType) {
228
+ return workType === 'FieldRules' ? 'Field Rules' : workType;
229
+ }
230
+ static ɵfac = /*@__PURE__*/ (() => { let ɵRecordProcessStudioComponent_BaseFactory; return function RecordProcessStudioComponent_Factory(__ngFactoryType__) { return (ɵRecordProcessStudioComponent_BaseFactory || (ɵRecordProcessStudioComponent_BaseFactory = i0.ɵɵgetInheritedFactory(RecordProcessStudioComponent)))(__ngFactoryType__ || RecordProcessStudioComponent); }; })();
231
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RecordProcessStudioComponent, selectors: [["mj-record-process-studio"]], inputs: { EntityID: "EntityID" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 2, consts: [[3, "DriverClass", "Context"], [1, "rps-bar"], [1, "rps-search"], [1, "fa-solid", "fa-magnifying-glass"], ["placeholder", "Search bulk operations\u2026", 1, "mj-input", 3, "input", "value"], [1, "rps-spacer"], ["mjButton", "", "variant", "flat", 3, "click"], [1, "fa-solid", "fa-arrows-rotate"], ["mjButton", "", "variant", "primary", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "rps-msg"], [1, "rps-empty"], [1, "rps-table"], [1, "fa-solid", "fa-circle-notch", "fa-spin"], [1, "fa-solid", "fa-list-check"], [1, "rps-tr", "rps-head"], [1, "ta-r"], [1, "rps-tr"], [1, "rps-tr", 3, "click"], [1, "rps-nm"], [1, "rps-sub"], [1, "pill", "purple"], [1, "pill"], [1, "ta-r", "rps-acts", 3, "click"], ["title", "Run", 1, "iconbtn", "run", 3, "click"], [1, "fa-solid", "fa-play"], ["title", "Edit", 1, "iconbtn", 3, "click"], [1, "fa-solid", "fa-pen"], [1, "rps-editbar"], [1, "fa-solid", "fa-arrow-left"], [3, "Saved", "Cancelled", "RecordProcessID", "DefaultEntityID", "Provider"], [3, "Completed", "Cancelled", "DriverClass", "Context"]], template: function RecordProcessStudioComponent_Template(rf, ctx) { if (rf & 1) {
232
+ i0.ɵɵconditionalCreate(0, RecordProcessStudioComponent_Conditional_0_Template, 14, 2)(1, RecordProcessStudioComponent_Conditional_1_Template, 7, 4);
233
+ i0.ɵɵconditionalCreate(2, RecordProcessStudioComponent_Conditional_2_Template, 1, 2, "mj-entity-action-ux-host", 0);
234
+ } if (rf & 2) {
235
+ i0.ɵɵconditional(ctx.Mode === "list" ? 0 : 1);
236
+ i0.ɵɵadvance(2);
237
+ i0.ɵɵconditional(ctx.RunDriver ? 2 : -1);
238
+ } }, dependencies: [RecordProcessEditorComponent, EntityActionUXHostComponent, MJButtonDirective], styles: ["[_nghost-%COMP%]{display:block;color:var(--mj-text-primary)}\n .rps-bar[_ngcontent-%COMP%]{display:flex;align-items:center;gap:12px;margin-bottom:16px}\n .rps-search[_ngcontent-%COMP%]{position:relative;flex:0 0 360px;max-width:360px}\n .rps-search[_ngcontent-%COMP%] i[_ngcontent-%COMP%]{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--mj-text-muted);font-size:13px}\n .rps-search[_ngcontent-%COMP%] input[_ngcontent-%COMP%]{padding-left:34px}\n .rps-spacer[_ngcontent-%COMP%]{flex:1}\n .rps-bar[_ngcontent-%COMP%] button[_ngcontent-%COMP%] i[_ngcontent-%COMP%], .rps-editbar[_ngcontent-%COMP%] button[_ngcontent-%COMP%] i[_ngcontent-%COMP%]{margin-right:6px}\n .rps-msg[_ngcontent-%COMP%], .rps-empty[_ngcontent-%COMP%]{padding:40px;text-align:center;color:var(--mj-text-muted)}\n .rps-empty[_ngcontent-%COMP%] i[_ngcontent-%COMP%]{font-size:30px;display:block;margin-bottom:12px;color:var(--mj-text-disabled)}\n .rps-empty[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{margin:0 0 16px}\n .rps-table[_ngcontent-%COMP%]{border:1px solid var(--mj-border-subtle);border-radius:var(--mj-radius-md,10px);overflow:hidden}\n .rps-tr[_ngcontent-%COMP%]{display:grid;grid-template-columns:2.2fr 1.2fr 1fr .8fr 1fr;gap:14px;align-items:center;padding:13px 16px;border-bottom:1px solid var(--mj-border-subtle);cursor:pointer;font-size:13.5px}\n .rps-tr[_ngcontent-%COMP%]:last-child{border-bottom:none}\n .rps-tr[_ngcontent-%COMP%]:not(.rps-head):hover{background:var(--mj-bg-surface-hover)}\n .rps-head[_ngcontent-%COMP%]{background:var(--mj-bg-surface-sunken);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--mj-text-muted);font-weight:700;cursor:default}\n .rps-nm[_ngcontent-%COMP%]{font-weight:600}\n .rps-sub[_ngcontent-%COMP%]{color:var(--mj-text-muted);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:420px}\n .ta-r[_ngcontent-%COMP%]{text-align:right;justify-self:end}\n .rps-acts[_ngcontent-%COMP%]{display:flex;gap:6px}\n .iconbtn[_ngcontent-%COMP%]{width:30px;height:30px;border-radius:7px;border:1px solid var(--mj-border-default);background:var(--mj-bg-surface-card);color:var(--mj-text-secondary);cursor:pointer;font-size:12.5px}\n .iconbtn[_ngcontent-%COMP%]:hover{background:var(--mj-bg-surface-hover);color:var(--mj-text-primary)}\n .iconbtn.run[_ngcontent-%COMP%]:hover{color:var(--mj-status-success-text);border-color:var(--mj-status-success)}\n .pill[_ngcontent-%COMP%]{display:inline-flex;align-items:center;padding:3px 10px;border-radius:999px;font-size:11.5px;font-weight:700;background:var(--mj-bg-surface-sunken);color:var(--mj-text-secondary)}\n .pill.purple[_ngcontent-%COMP%]{background:color-mix(in srgb,var(--mj-brand-primary) 14%,transparent);color:var(--mj-brand-primary)}\n .pill.ok[_ngcontent-%COMP%]{background:var(--mj-status-success-bg);color:var(--mj-status-success-text)}\n .pill.warn[_ngcontent-%COMP%]{background:var(--mj-status-warning-bg);color:var(--mj-status-warning-text)}\n .pill.muted[_ngcontent-%COMP%]{background:var(--mj-bg-surface-sunken);color:var(--mj-text-muted)}\n .rps-editbar[_ngcontent-%COMP%]{display:flex;align-items:center;gap:14px;margin-bottom:18px}\n .rps-editbar[_ngcontent-%COMP%] h2[_ngcontent-%COMP%]{margin:0;font-size:18px;font-weight:700}"], changeDetection: 0 });
239
+ }
240
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordProcessStudioComponent, [{
241
+ type: Component,
242
+ args: [{ selector: 'mj-record-process-studio', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [RecordProcessEditorComponent, EntityActionUXHostComponent, MJButtonDirective], template: `
243
+ @if (Mode === 'list') {
244
+ <div class="rps-bar">
245
+ <div class="rps-search"><i class="fa-solid fa-magnifying-glass"></i>
246
+ <input class="mj-input" placeholder="Search bulk operations…" [value]="Search" (input)="onSearch($event)"></div>
247
+ <span class="rps-spacer"></span>
248
+ <button mjButton variant="flat" (click)="reload()"><i class="fa-solid fa-arrows-rotate"></i> Refresh</button>
249
+ <button mjButton variant="primary" (click)="New()"><i class="fa-solid fa-plus"></i> New operation</button>
250
+ </div>
251
+
252
+ @if (Loading) {
253
+ <div class="rps-msg"><i class="fa-solid fa-circle-notch fa-spin"></i> Loading…</div>
254
+ } @else if (Filtered.length === 0) {
255
+ <div class="rps-empty">
256
+ <i class="fa-solid fa-list-check"></i>
257
+ <p>No bulk operations yet.</p>
258
+ <button mjButton variant="primary" (click)="New()"><i class="fa-solid fa-plus"></i> Create one</button>
259
+ </div>
260
+ } @else {
261
+ <div class="rps-table">
262
+ <div class="rps-tr rps-head">
263
+ <div>Operation</div><div>Entity</div><div>Type</div><div>Status</div><div class="ta-r">Actions</div>
264
+ </div>
265
+ @for (p of Filtered; track p.ID) {
266
+ <div class="rps-tr" (click)="Edit(p)">
267
+ <div><div class="rps-nm">{{ p.Name }}</div>@if (p.Description) {<div class="rps-sub">{{ p.Description }}</div>}</div>
268
+ <div>{{ p.Entity }}</div>
269
+ <div><span class="pill purple">{{ workLabel(p.WorkType) }}</span></div>
270
+ <div><span class="pill" [class.ok]="p.Status==='Active'" [class.warn]="p.Status==='Draft'" [class.muted]="p.Status==='Disabled'">{{ p.Status }}</span></div>
271
+ <div class="ta-r rps-acts" (click)="$event.stopPropagation()">
272
+ <button class="iconbtn run" title="Run" (click)="Run(p)"><i class="fa-solid fa-play"></i></button>
273
+ <button class="iconbtn" title="Edit" (click)="Edit(p)"><i class="fa-solid fa-pen"></i></button>
274
+ </div>
275
+ </div>
276
+ }
277
+ </div>
278
+ }
279
+ } @else {
280
+ <div class="rps-editbar">
281
+ <button mjButton variant="flat" (click)="backToList()"><i class="fa-solid fa-arrow-left"></i> Back</button>
282
+ <h2>{{ EditingID ? 'Edit operation' : 'New operation' }}</h2>
283
+ </div>
284
+ <mj-record-process-editor
285
+ [RecordProcessID]="EditingID"
286
+ [DefaultEntityID]="EntityID"
287
+ [Provider]="Provider"
288
+ (Saved)="onSaved()"
289
+ (Cancelled)="backToList()">
290
+ </mj-record-process-editor>
291
+ }
292
+
293
+ @if (RunDriver) {
294
+ <mj-entity-action-ux-host
295
+ [DriverClass]="RunDriver.DriverClass" [Context]="RunDriver.Context"
296
+ (Completed)="onRunDone($event)" (Cancelled)="RunDriver = null">
297
+ </mj-entity-action-ux-host>
298
+ }
299
+ `, styles: ["\n :host{display:block;color:var(--mj-text-primary)}\n .rps-bar{display:flex;align-items:center;gap:12px;margin-bottom:16px}\n .rps-search{position:relative;flex:0 0 360px;max-width:360px}\n .rps-search i{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--mj-text-muted);font-size:13px}\n .rps-search input{padding-left:34px}\n .rps-spacer{flex:1}\n .rps-bar button i,.rps-editbar button i{margin-right:6px}\n .rps-msg,.rps-empty{padding:40px;text-align:center;color:var(--mj-text-muted)}\n .rps-empty i{font-size:30px;display:block;margin-bottom:12px;color:var(--mj-text-disabled)}\n .rps-empty p{margin:0 0 16px}\n .rps-table{border:1px solid var(--mj-border-subtle);border-radius:var(--mj-radius-md,10px);overflow:hidden}\n .rps-tr{display:grid;grid-template-columns:2.2fr 1.2fr 1fr .8fr 1fr;gap:14px;align-items:center;padding:13px 16px;border-bottom:1px solid var(--mj-border-subtle);cursor:pointer;font-size:13.5px}\n .rps-tr:last-child{border-bottom:none}\n .rps-tr:not(.rps-head):hover{background:var(--mj-bg-surface-hover)}\n .rps-head{background:var(--mj-bg-surface-sunken);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--mj-text-muted);font-weight:700;cursor:default}\n .rps-nm{font-weight:600}\n .rps-sub{color:var(--mj-text-muted);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:420px}\n .ta-r{text-align:right;justify-self:end}\n .rps-acts{display:flex;gap:6px}\n .iconbtn{width:30px;height:30px;border-radius:7px;border:1px solid var(--mj-border-default);background:var(--mj-bg-surface-card);color:var(--mj-text-secondary);cursor:pointer;font-size:12.5px}\n .iconbtn:hover{background:var(--mj-bg-surface-hover);color:var(--mj-text-primary)}\n .iconbtn.run:hover{color:var(--mj-status-success-text);border-color:var(--mj-status-success)}\n .pill{display:inline-flex;align-items:center;padding:3px 10px;border-radius:999px;font-size:11.5px;font-weight:700;background:var(--mj-bg-surface-sunken);color:var(--mj-text-secondary)}\n .pill.purple{background:color-mix(in srgb,var(--mj-brand-primary) 14%,transparent);color:var(--mj-brand-primary)}\n .pill.ok{background:var(--mj-status-success-bg);color:var(--mj-status-success-text)}\n .pill.warn{background:var(--mj-status-warning-bg);color:var(--mj-status-warning-text)}\n .pill.muted{background:var(--mj-bg-surface-sunken);color:var(--mj-text-muted)}\n .rps-editbar{display:flex;align-items:center;gap:14px;margin-bottom:18px}\n .rps-editbar h2{margin:0;font-size:18px;font-weight:700}\n "] }]
300
+ }], null, { EntityID: [{
301
+ type: Input
302
+ }] }); })();
303
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RecordProcessStudioComponent, { className: "RecordProcessStudioComponent", filePath: "lib/record-process-studio/record-process-studio.component.ts", lineNumber: 125 }); })();
304
+ //# sourceMappingURL=record-process-studio.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-process-studio.component.js","sourceRoot":"","sources":["../../../src/lib/record-process-studio/record-process-studio.component.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview The Bulk Operations hub: lists the org's Record Processes, lets a user create / edit one\n * (via the embedded {@link RecordProcessEditorComponent}), and run any of them on demand (mounting the\n * same dry-run → confirm runner used from a grid). Generic + reusable — Explorer's Bulk Operations app\n * hosts it, but any MJ app can drop it in.\n * @module @memberjunction/ng-record-process-studio\n */\nimport { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { RunView } from '@memberjunction/core';\nimport { EntityActionUXHostComponent, type EntityActionUXContext, type EntityActionUXResult } from '@memberjunction/ng-entity-action-ux';\nimport { MJButtonDirective } from '@memberjunction/ng-ui-components';\nimport { RecordProcessEditorComponent } from '../record-process-editor/record-process-editor.component';\n\n/** A row of the process list (read-only projection). */\ninterface ProcessRow {\n ID: string;\n Name: string;\n Description?: string;\n Entity: string;\n EntityID: string;\n WorkType: string;\n Status: string;\n ScopeType: string;\n ScopeFilter?: string;\n ScopeViewID?: string;\n ScopeListID?: string;\n}\n\n@Component({\n selector: 'mj-record-process-studio',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RecordProcessEditorComponent, EntityActionUXHostComponent, MJButtonDirective],\n template: `\n @if (Mode === 'list') {\n <div class=\"rps-bar\">\n <div class=\"rps-search\"><i class=\"fa-solid fa-magnifying-glass\"></i>\n <input class=\"mj-input\" placeholder=\"Search bulk operations…\" [value]=\"Search\" (input)=\"onSearch($event)\"></div>\n <span class=\"rps-spacer\"></span>\n <button mjButton variant=\"flat\" (click)=\"reload()\"><i class=\"fa-solid fa-arrows-rotate\"></i> Refresh</button>\n <button mjButton variant=\"primary\" (click)=\"New()\"><i class=\"fa-solid fa-plus\"></i> New operation</button>\n </div>\n\n @if (Loading) {\n <div class=\"rps-msg\"><i class=\"fa-solid fa-circle-notch fa-spin\"></i> Loading…</div>\n } @else if (Filtered.length === 0) {\n <div class=\"rps-empty\">\n <i class=\"fa-solid fa-list-check\"></i>\n <p>No bulk operations yet.</p>\n <button mjButton variant=\"primary\" (click)=\"New()\"><i class=\"fa-solid fa-plus\"></i> Create one</button>\n </div>\n } @else {\n <div class=\"rps-table\">\n <div class=\"rps-tr rps-head\">\n <div>Operation</div><div>Entity</div><div>Type</div><div>Status</div><div class=\"ta-r\">Actions</div>\n </div>\n @for (p of Filtered; track p.ID) {\n <div class=\"rps-tr\" (click)=\"Edit(p)\">\n <div><div class=\"rps-nm\">{{ p.Name }}</div>@if (p.Description) {<div class=\"rps-sub\">{{ p.Description }}</div>}</div>\n <div>{{ p.Entity }}</div>\n <div><span class=\"pill purple\">{{ workLabel(p.WorkType) }}</span></div>\n <div><span class=\"pill\" [class.ok]=\"p.Status==='Active'\" [class.warn]=\"p.Status==='Draft'\" [class.muted]=\"p.Status==='Disabled'\">{{ p.Status }}</span></div>\n <div class=\"ta-r rps-acts\" (click)=\"$event.stopPropagation()\">\n <button class=\"iconbtn run\" title=\"Run\" (click)=\"Run(p)\"><i class=\"fa-solid fa-play\"></i></button>\n <button class=\"iconbtn\" title=\"Edit\" (click)=\"Edit(p)\"><i class=\"fa-solid fa-pen\"></i></button>\n </div>\n </div>\n }\n </div>\n }\n } @else {\n <div class=\"rps-editbar\">\n <button mjButton variant=\"flat\" (click)=\"backToList()\"><i class=\"fa-solid fa-arrow-left\"></i> Back</button>\n <h2>{{ EditingID ? 'Edit operation' : 'New operation' }}</h2>\n </div>\n <mj-record-process-editor\n [RecordProcessID]=\"EditingID\"\n [DefaultEntityID]=\"EntityID\"\n [Provider]=\"Provider\"\n (Saved)=\"onSaved()\"\n (Cancelled)=\"backToList()\">\n </mj-record-process-editor>\n }\n\n @if (RunDriver) {\n <mj-entity-action-ux-host\n [DriverClass]=\"RunDriver.DriverClass\" [Context]=\"RunDriver.Context\"\n (Completed)=\"onRunDone($event)\" (Cancelled)=\"RunDriver = null\">\n </mj-entity-action-ux-host>\n }\n `,\n styles: [`\n :host{display:block;color:var(--mj-text-primary)}\n .rps-bar{display:flex;align-items:center;gap:12px;margin-bottom:16px}\n .rps-search{position:relative;flex:0 0 360px;max-width:360px}\n .rps-search i{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--mj-text-muted);font-size:13px}\n .rps-search input{padding-left:34px}\n .rps-spacer{flex:1}\n .rps-bar button i,.rps-editbar button i{margin-right:6px}\n .rps-msg,.rps-empty{padding:40px;text-align:center;color:var(--mj-text-muted)}\n .rps-empty i{font-size:30px;display:block;margin-bottom:12px;color:var(--mj-text-disabled)}\n .rps-empty p{margin:0 0 16px}\n .rps-table{border:1px solid var(--mj-border-subtle);border-radius:var(--mj-radius-md,10px);overflow:hidden}\n .rps-tr{display:grid;grid-template-columns:2.2fr 1.2fr 1fr .8fr 1fr;gap:14px;align-items:center;padding:13px 16px;border-bottom:1px solid var(--mj-border-subtle);cursor:pointer;font-size:13.5px}\n .rps-tr:last-child{border-bottom:none}\n .rps-tr:not(.rps-head):hover{background:var(--mj-bg-surface-hover)}\n .rps-head{background:var(--mj-bg-surface-sunken);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--mj-text-muted);font-weight:700;cursor:default}\n .rps-nm{font-weight:600}\n .rps-sub{color:var(--mj-text-muted);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:420px}\n .ta-r{text-align:right;justify-self:end}\n .rps-acts{display:flex;gap:6px}\n .iconbtn{width:30px;height:30px;border-radius:7px;border:1px solid var(--mj-border-default);background:var(--mj-bg-surface-card);color:var(--mj-text-secondary);cursor:pointer;font-size:12.5px}\n .iconbtn:hover{background:var(--mj-bg-surface-hover);color:var(--mj-text-primary)}\n .iconbtn.run:hover{color:var(--mj-status-success-text);border-color:var(--mj-status-success)}\n .pill{display:inline-flex;align-items:center;padding:3px 10px;border-radius:999px;font-size:11.5px;font-weight:700;background:var(--mj-bg-surface-sunken);color:var(--mj-text-secondary)}\n .pill.purple{background:color-mix(in srgb,var(--mj-brand-primary) 14%,transparent);color:var(--mj-brand-primary)}\n .pill.ok{background:var(--mj-status-success-bg);color:var(--mj-status-success-text)}\n .pill.warn{background:var(--mj-status-warning-bg);color:var(--mj-status-warning-text)}\n .pill.muted{background:var(--mj-bg-surface-sunken);color:var(--mj-text-muted)}\n .rps-editbar{display:flex;align-items:center;gap:14px;margin-bottom:18px}\n .rps-editbar h2{margin:0;font-size:18px;font-weight:700}\n `],\n})\nexport class RecordProcessStudioComponent extends BaseAngularComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n\n /** Optional: restrict the hub to processes for one entity (e.g. when embedded on an entity page). */\n @Input() EntityID: string | null = null;\n\n public Mode: 'list' | 'edit' = 'list';\n public EditingID: string | null = null;\n public Loading = true;\n public Search = '';\n public Processes: ProcessRow[] = [];\n public RunDriver: { DriverClass: string; Context: EntityActionUXContext } | null = null;\n\n async ngOnInit(): Promise<void> {\n await this.reload();\n }\n\n get Filtered(): ProcessRow[] {\n const q = this.Search.trim().toLowerCase();\n if (!q) return this.Processes;\n return this.Processes.filter((p) => p.Name.toLowerCase().includes(q) || (p.Entity ?? '').toLowerCase().includes(q));\n }\n\n async reload(): Promise<void> {\n this.Loading = true; this.cdr.detectChanges();\n const rv = this.Provider ? RunView.FromMetadataProvider(this.Provider) : new RunView();\n const filter = this.EntityID ? `EntityID='${this.EntityID}'` : '';\n const result = await rv.RunView<ProcessRow>({\n EntityName: 'MJ: Record Processes',\n Fields: ['ID', 'Name', 'Description', 'Entity', 'EntityID', 'WorkType', 'Status', 'ScopeType', 'ScopeFilter', 'ScopeViewID', 'ScopeListID'],\n ExtraFilter: filter,\n OrderBy: 'Name',\n ResultType: 'simple',\n });\n this.Processes = result.Success ? (result.Results ?? []) : [];\n this.Loading = false;\n this.cdr.detectChanges();\n }\n\n onSearch(event: Event): void { this.Search = (event.target as HTMLInputElement).value; this.cdr.detectChanges(); }\n\n New(): void { this.EditingID = null; this.Mode = 'edit'; this.cdr.detectChanges(); }\n Edit(p: ProcessRow): void { this.EditingID = p.ID; this.Mode = 'edit'; this.cdr.detectChanges(); }\n backToList(): void { this.Mode = 'list'; this.EditingID = null; this.cdr.detectChanges(); }\n async onSaved(): Promise<void> { this.backToList(); await this.reload(); }\n\n /** Runs a process on demand against its default scope, via the dry-run → confirm runner. */\n Run(p: ProcessRow): void {\n const entity = this.ProviderToUse.EntityByID(p.EntityID);\n if (!entity) return;\n this.RunDriver = {\n DriverClass: 'RecordProcessRunnerUX',\n Context: {\n EntityInfo: entity,\n ScopeKind: (p.ScopeType?.toLowerCase() as EntityActionUXContext['ScopeKind']) || 'filter',\n Filter: p.ScopeFilter ?? undefined,\n ViewID: p.ScopeViewID ?? undefined,\n ListID: p.ScopeListID ?? undefined,\n Config: { RecordProcessID: p.ID },\n Provider: this.ProviderToUse,\n ActionLabel: p.Name,\n },\n };\n this.cdr.detectChanges();\n }\n\n onRunDone(_r: EntityActionUXResult): void { this.RunDriver = null; this.cdr.detectChanges(); }\n\n workLabel(workType: string): string {\n return workType === 'FieldRules' ? 'Field Rules' : workType;\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview Pure helpers for reading a Process Run Detail's persisted result payload (the per-record
3
+ * field diff) and rendering values. Extracted from the history component so the audit-diff logic is
4
+ * unit-testable without a TestBed.
5
+ * @module @memberjunction/ng-record-process-studio
6
+ */
7
+ /** One computed field change, as persisted on `MJ: Process Run Details.ResultPayload`. */
8
+ export interface RunDetailChange {
9
+ Field: string;
10
+ OldValue: unknown;
11
+ NewValue: unknown;
12
+ Applied: boolean;
13
+ Changed: boolean;
14
+ Error?: string;
15
+ }
16
+ /**
17
+ * Parses a Process Run Detail's `ResultPayload` and returns only the changes that were actually applied
18
+ * (condition passed, value differed, no error). Safe on null/empty/invalid JSON — returns `[]`.
19
+ */
20
+ export declare function parseAppliedRunDetailChanges(resultPayload: string | null | undefined): RunDetailChange[];
21
+ /** Renders an arbitrary value for the audit diff table. */
22
+ export declare function displayRunValue(value: unknown): string;
23
+ //# sourceMappingURL=run-detail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-detail.d.ts","sourceRoot":"","sources":["../../src/lib/run-detail.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AASD;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,eAAe,EAAE,CAWxG;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQtD"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @fileoverview Pure helpers for reading a Process Run Detail's persisted result payload (the per-record
3
+ * field diff) and rendering values. Extracted from the history component so the audit-diff logic is
4
+ * unit-testable without a TestBed.
5
+ * @module @memberjunction/ng-record-process-studio
6
+ */
7
+ /**
8
+ * Parses a Process Run Detail's `ResultPayload` and returns only the changes that were actually applied
9
+ * (condition passed, value differed, no error). Safe on null/empty/invalid JSON — returns `[]`.
10
+ */
11
+ export function parseAppliedRunDetailChanges(resultPayload) {
12
+ if (!resultPayload) {
13
+ return [];
14
+ }
15
+ let payload;
16
+ try {
17
+ payload = JSON.parse(resultPayload);
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ return (payload?.Changes ?? []).filter((c) => c.Applied && c.Changed && !c.Error);
23
+ }
24
+ /** Renders an arbitrary value for the audit diff table. */
25
+ export function displayRunValue(value) {
26
+ if (value === null || value === undefined || value === '') {
27
+ return '(empty)';
28
+ }
29
+ if (typeof value === 'object') {
30
+ return JSON.stringify(value);
31
+ }
32
+ return String(value);
33
+ }
34
+ //# sourceMappingURL=run-detail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-detail.js","sourceRoot":"","sources":["../../src/lib/run-detail.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmBH;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAAC,aAAwC;IACjF,IAAI,CAAC,aAAa,EAAE,CAAC;QACjB,OAAO,EAAE,CAAC;IACd,CAAC;IACD,IAAI,OAAqC,CAAC;IAC1C,IAAI,CAAC;QACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAqB,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;IACD,OAAO,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AACtF,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,eAAe,CAAC,KAAc;IAC1C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACxD,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC","sourcesContent":["/**\n * @fileoverview Pure helpers for reading a Process Run Detail's persisted result payload (the per-record\n * field diff) and rendering values. Extracted from the history component so the audit-diff logic is\n * unit-testable without a TestBed.\n * @module @memberjunction/ng-record-process-studio\n */\n\n/** One computed field change, as persisted on `MJ: Process Run Details.ResultPayload`. */\nexport interface RunDetailChange {\n Field: string;\n OldValue: unknown;\n NewValue: unknown;\n Applied: boolean;\n Changed: boolean;\n Error?: string;\n}\n\n/** The JSON shape persisted for a FieldRules run detail. */\ninterface RunDetailPayload {\n DryRun: boolean;\n Changes: RunDetailChange[];\n ChangedFields: string[];\n}\n\n/**\n * Parses a Process Run Detail's `ResultPayload` and returns only the changes that were actually applied\n * (condition passed, value differed, no error). Safe on null/empty/invalid JSON — returns `[]`.\n */\nexport function parseAppliedRunDetailChanges(resultPayload: string | null | undefined): RunDetailChange[] {\n if (!resultPayload) {\n return [];\n }\n let payload: RunDetailPayload | undefined;\n try {\n payload = JSON.parse(resultPayload) as RunDetailPayload;\n } catch {\n return [];\n }\n return (payload?.Changes ?? []).filter((c) => c.Applied && c.Changed && !c.Error);\n}\n\n/** Renders an arbitrary value for the audit diff table. */\nexport function displayRunValue(value: unknown): string {\n if (value === null || value === undefined || value === '') {\n return '(empty)';\n }\n if (typeof value === 'object') {\n return JSON.stringify(value);\n }\n return String(value);\n}\n"]}
@@ -0,0 +1,9 @@
1
+ export * from './lib/record-process-editor/record-process-editor.component';
2
+ export * from './lib/record-process-studio/record-process-studio.component';
3
+ export * from './lib/record-process-history/record-process-history.component';
4
+ /**
5
+ * Tree-shaking guard — reference the studio components so a consumer's bundler keeps them.
6
+ * Call once from the consuming app's bootstrap.
7
+ */
8
+ export declare function LoadRecordProcessStudio(): void;
9
+ //# sourceMappingURL=public-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAGA,cAAc,6DAA6D,CAAC;AAC5E,cAAc,6DAA6D,CAAC;AAC5E,cAAc,+DAA+D,CAAC;AAM9E;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAK9C"}
@@ -0,0 +1,19 @@
1
+ // @memberjunction/ng-record-process-studio
2
+ // Generic Bulk Operations studio — author, run, and audit Record Processes.
3
+ export * from './lib/record-process-editor/record-process-editor.component';
4
+ export * from './lib/record-process-studio/record-process-studio.component';
5
+ export * from './lib/record-process-history/record-process-history.component';
6
+ import { RecordProcessEditorComponent } from './lib/record-process-editor/record-process-editor.component';
7
+ import { RecordProcessStudioComponent } from './lib/record-process-studio/record-process-studio.component';
8
+ import { RecordProcessHistoryComponent } from './lib/record-process-history/record-process-history.component';
9
+ /**
10
+ * Tree-shaking guard — reference the studio components so a consumer's bundler keeps them.
11
+ * Call once from the consuming app's bootstrap.
12
+ */
13
+ export function LoadRecordProcessStudio() {
14
+ const _keep = [RecordProcessEditorComponent, RecordProcessStudioComponent, RecordProcessHistoryComponent];
15
+ if (_keep.length === 0) {
16
+ throw new Error('unreachable');
17
+ }
18
+ }
19
+ //# sourceMappingURL=public-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,4EAA4E;AAE5E,cAAc,6DAA6D,CAAC;AAC5E,cAAc,6DAA6D,CAAC;AAC5E,cAAc,+DAA+D,CAAC;AAE9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,6DAA6D,CAAC;AAC3G,OAAO,EAAE,4BAA4B,EAAE,MAAM,6DAA6D,CAAC;AAC3G,OAAO,EAAE,6BAA6B,EAAE,MAAM,+DAA+D,CAAC;AAE9G;;;GAGG;AACH,MAAM,UAAU,uBAAuB;IACnC,MAAM,KAAK,GAAG,CAAC,4BAA4B,EAAE,4BAA4B,EAAE,6BAA6B,CAAC,CAAC;IAC1G,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC;IACnC,CAAC;AACL,CAAC","sourcesContent":["// @memberjunction/ng-record-process-studio\n// Generic Bulk Operations studio — author, run, and audit Record Processes.\n\nexport * from './lib/record-process-editor/record-process-editor.component';\nexport * from './lib/record-process-studio/record-process-studio.component';\nexport * from './lib/record-process-history/record-process-history.component';\n\nimport { RecordProcessEditorComponent } from './lib/record-process-editor/record-process-editor.component';\nimport { RecordProcessStudioComponent } from './lib/record-process-studio/record-process-studio.component';\nimport { RecordProcessHistoryComponent } from './lib/record-process-history/record-process-history.component';\n\n/**\n * Tree-shaking guard — reference the studio components so a consumer's bundler keeps them.\n * Call once from the consuming app's bootstrap.\n */\nexport function LoadRecordProcessStudio(): void {\n const _keep = [RecordProcessEditorComponent, RecordProcessStudioComponent, RecordProcessHistoryComponent];\n if (_keep.length === 0) {\n throw new Error('unreachable');\n }\n}\n"]}
package/package.json CHANGED
@@ -1,10 +1,52 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-record-process-studio",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @memberjunction/ng-record-process-studio",
3
+ "version": "5.43.0",
4
+ "description": "MemberJunction: Bulk Operations studio — generic, reusable Angular UI for authoring, running, and auditing Record Processes (rules-driven bulk updates). Consumed by MJExplorer's Bulk Operations app or any MJ app.",
5
+ "main": "./dist/public-api.js",
6
+ "typings": "./dist/public-api.d.ts",
7
+ "files": [
8
+ "/dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "npm run clean && ngc",
12
+ "clean": "rimraf dist",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest"
15
+ },
5
16
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
17
+ "MemberJunction",
18
+ "Angular",
19
+ "Bulk Operations",
20
+ "Record Process",
21
+ "Field Rules"
22
+ ],
23
+ "author": "MemberJunction.com",
24
+ "license": "ISC",
25
+ "devDependencies": {
26
+ "@angular/compiler": "21.1.3",
27
+ "@angular/compiler-cli": "21.1.3",
28
+ "rimraf": "^5.0.0",
29
+ "vitest": "^4.0.18"
30
+ },
31
+ "peerDependencies": {
32
+ "@angular/common": "21.1.3",
33
+ "@angular/core": "21.1.3",
34
+ "@angular/forms": "21.1.3",
35
+ "rxjs": "^7.8.2"
36
+ },
37
+ "dependencies": {
38
+ "@memberjunction/core": "5.43.0",
39
+ "@memberjunction/core-entities": "5.43.0",
40
+ "@memberjunction/global": "5.43.0",
41
+ "@memberjunction/graphql-dataprovider": "5.43.0",
42
+ "@memberjunction/ng-base-types": "5.43.0",
43
+ "@memberjunction/ng-entity-action-ux": "5.43.0",
44
+ "@memberjunction/ng-ui-components": "5.43.0",
45
+ "tslib": "^2.8.1"
46
+ },
47
+ "sideEffects": false,
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/MemberJunction/MJ"
51
+ }
10
52
  }