@memberjunction/ng-dashboards 5.8.0 → 5.10.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.
- package/dist/AI/components/models/model-management.component.d.ts +2 -0
- package/dist/AI/components/models/model-management.component.d.ts.map +1 -1
- package/dist/AI/components/models/model-management.component.js +44 -2
- package/dist/AI/components/models/model-management.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js +15 -12
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js.map +1 -1
- package/dist/DataExplorer/components/view-selector/view-selector.component.d.ts.map +1 -1
- package/dist/DataExplorer/components/view-selector/view-selector.component.js +16 -22
- package/dist/DataExplorer/components/view-selector/view-selector.component.js.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +31 -11
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/Home/home-dashboard.component.d.ts.map +1 -1
- package/dist/Home/home-dashboard.component.js +10 -7
- package/dist/Home/home-dashboard.component.js.map +1 -1
- package/dist/Integration/components/activity/activity.component.d.ts +96 -0
- package/dist/Integration/components/activity/activity.component.d.ts.map +1 -0
- package/dist/Integration/components/activity/activity.component.js +961 -0
- package/dist/Integration/components/activity/activity.component.js.map +1 -0
- package/dist/Integration/components/connections/connections.component.d.ts +194 -0
- package/dist/Integration/components/connections/connections.component.d.ts.map +1 -0
- package/dist/Integration/components/connections/connections.component.js +2368 -0
- package/dist/Integration/components/connections/connections.component.js.map +1 -0
- package/dist/Integration/components/mapping-workspace/mapping-workspace.component.d.ts +213 -15
- package/dist/Integration/components/mapping-workspace/mapping-workspace.component.d.ts.map +1 -1
- package/dist/Integration/components/mapping-workspace/mapping-workspace.component.js +2093 -187
- package/dist/Integration/components/mapping-workspace/mapping-workspace.component.js.map +1 -1
- package/dist/Integration/components/overview/overview.component.d.ts +60 -0
- package/dist/Integration/components/overview/overview.component.d.ts.map +1 -0
- package/dist/Integration/components/overview/overview.component.js +628 -0
- package/dist/Integration/components/overview/overview.component.js.map +1 -0
- package/dist/Integration/components/pipelines/pipelines.component.d.ts +203 -0
- package/dist/Integration/components/pipelines/pipelines.component.d.ts.map +1 -0
- package/dist/Integration/components/pipelines/pipelines.component.js +2057 -0
- package/dist/Integration/components/pipelines/pipelines.component.js.map +1 -0
- package/dist/Integration/components/schedules/schedules.component.d.ts +110 -0
- package/dist/Integration/components/schedules/schedules.component.d.ts.map +1 -0
- package/dist/Integration/components/schedules/schedules.component.js +842 -0
- package/dist/Integration/components/schedules/schedules.component.js.map +1 -0
- package/dist/Integration/components/visual-editor/visual-editor.component.d.ts +141 -0
- package/dist/Integration/components/visual-editor/visual-editor.component.d.ts.map +1 -0
- package/dist/Integration/components/visual-editor/visual-editor.component.js +1538 -0
- package/dist/Integration/components/visual-editor/visual-editor.component.js.map +1 -0
- package/dist/Integration/components/widgets/run-history-panel.component.js +3 -2
- package/dist/Integration/components/widgets/run-history-panel.component.js.map +1 -1
- package/dist/Integration/index.d.ts +5 -3
- package/dist/Integration/index.d.ts.map +1 -1
- package/dist/Integration/index.js +11 -7
- package/dist/Integration/index.js.map +1 -1
- package/dist/Integration/integration.module.d.ts +20 -16
- package/dist/Integration/integration.module.d.ts.map +1 -1
- package/dist/Integration/integration.module.js +40 -21
- package/dist/Integration/integration.module.js.map +1 -1
- package/dist/Integration/services/integration-data.service.d.ts +123 -9
- package/dist/Integration/services/integration-data.service.d.ts.map +1 -1
- package/dist/Integration/services/integration-data.service.js +385 -68
- package/dist/Integration/services/integration-data.service.js.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.d.ts +27 -4
- package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.js +338 -144
- package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
- package/dist/__tests__/integration-data-service.test.js.map +1 -1
- package/dist/__tests__/mapping-validation.test.d.ts +2 -0
- package/dist/__tests__/mapping-validation.test.d.ts.map +1 -0
- package/dist/__tests__/mapping-validation.test.js +170 -0
- package/dist/__tests__/mapping-validation.test.js.map +1 -0
- package/package.json +40 -38
- package/dist/Integration/components/connection-studio/connection-studio.component.d.ts +0 -81
- package/dist/Integration/components/connection-studio/connection-studio.component.d.ts.map +0 -1
- package/dist/Integration/components/connection-studio/connection-studio.component.js +0 -960
- package/dist/Integration/components/connection-studio/connection-studio.component.js.map +0 -1
- package/dist/Integration/components/control-tower/control-tower.component.d.ts +0 -43
- package/dist/Integration/components/control-tower/control-tower.component.d.ts.map +0 -1
- package/dist/Integration/components/control-tower/control-tower.component.js +0 -446
- package/dist/Integration/components/control-tower/control-tower.component.js.map +0 -1
- package/dist/Integration/components/sync-activity/sync-activity.component.d.ts +0 -65
- package/dist/Integration/components/sync-activity/sync-activity.component.d.ts.map +0 -1
- package/dist/Integration/components/sync-activity/sync-activity.component.js +0 -671
- package/dist/Integration/components/sync-activity/sync-activity.component.js.map +0 -1
- package/dist/__tests__/connection-studio.test.d.ts +0 -2
- package/dist/__tests__/connection-studio.test.d.ts.map +0 -1
- package/dist/__tests__/connection-studio.test.js +0 -186
- package/dist/__tests__/connection-studio.test.js.map +0 -1
|
@@ -1,59 +1,63 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
|
-
import { RunView } from '@memberjunction/core';
|
|
2
|
+
import { Metadata, RunView } from '@memberjunction/core';
|
|
3
3
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
4
|
+
import { IntegrationEngineBase } from '@memberjunction/integration-engine-base';
|
|
5
|
+
import { GraphQLIntegrationClient, GraphQLActionClient } from '@memberjunction/graphql-dataprovider';
|
|
4
6
|
import * as i0 from "@angular/core";
|
|
7
|
+
/** Shared icon resolution map for integration names to Font Awesome icon classes */
|
|
8
|
+
const INTEGRATION_ICON_MAP = [
|
|
9
|
+
{ Pattern: /hubspot/i, Icon: 'fa-brands fa-hubspot' },
|
|
10
|
+
{ Pattern: /salesforce/i, Icon: 'fa-brands fa-salesforce' },
|
|
11
|
+
{ Pattern: /google/i, Icon: 'fa-brands fa-google' },
|
|
12
|
+
{ Pattern: /microsoft|dynamics|azure/i, Icon: 'fa-brands fa-microsoft' },
|
|
13
|
+
{ Pattern: /slack/i, Icon: 'fa-brands fa-slack' },
|
|
14
|
+
{ Pattern: /jira|atlassian/i, Icon: 'fa-brands fa-atlassian' },
|
|
15
|
+
{ Pattern: /github/i, Icon: 'fa-brands fa-github' },
|
|
16
|
+
{ Pattern: /stripe/i, Icon: 'fa-brands fa-stripe' },
|
|
17
|
+
{ Pattern: /shopify/i, Icon: 'fa-brands fa-shopify' },
|
|
18
|
+
{ Pattern: /mailchimp/i, Icon: 'fa-brands fa-mailchimp' },
|
|
19
|
+
{ Pattern: /wordpress/i, Icon: 'fa-brands fa-wordpress' },
|
|
20
|
+
{ Pattern: /dropbox/i, Icon: 'fa-brands fa-dropbox' },
|
|
21
|
+
{ Pattern: /csv|file|import/i, Icon: 'fa-solid fa-file-csv' },
|
|
22
|
+
{ Pattern: /postgres|mysql|sql|database|db/i, Icon: 'fa-solid fa-database' },
|
|
23
|
+
{ Pattern: /api|rest|graphql/i, Icon: 'fa-solid fa-code' },
|
|
24
|
+
{ Pattern: /member|membership/i, Icon: 'fa-solid fa-users' },
|
|
25
|
+
{ Pattern: /email|mail/i, Icon: 'fa-solid fa-envelope' },
|
|
26
|
+
{ Pattern: /calendar|event/i, Icon: 'fa-solid fa-calendar' }
|
|
27
|
+
];
|
|
28
|
+
/** Resolve an integration name to a Font Awesome icon class */
|
|
29
|
+
export function ResolveIntegrationIcon(name) {
|
|
30
|
+
const match = INTEGRATION_ICON_MAP.find(m => m.Pattern.test(name));
|
|
31
|
+
return match ? match.Icon : 'fa-solid fa-plug';
|
|
32
|
+
}
|
|
5
33
|
export class IntegrationDataService {
|
|
6
34
|
async LoadIntegrationSummaries(provider) {
|
|
35
|
+
const engine = IntegrationEngineBase.Instance;
|
|
7
36
|
const rv = this.createRunView(provider);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
OrderBy: 'Name',
|
|
13
|
-
Fields: ['ID', 'Name', 'IsActive', 'LastRunID',
|
|
14
|
-
'LastRunStartedAt', 'LastRunEndedAt', 'Company', 'Integration',
|
|
15
|
-
'DriverClassName'],
|
|
16
|
-
ResultType: 'simple'
|
|
17
|
-
},
|
|
18
|
-
{
|
|
37
|
+
// Engine provides cached metadata; only runs are dynamic and need a RunView call
|
|
38
|
+
const [, runsResult] = await Promise.all([
|
|
39
|
+
engine.Config(false),
|
|
40
|
+
rv.RunView({
|
|
19
41
|
EntityName: 'MJ: Company Integration Runs',
|
|
20
42
|
ExtraFilter: '',
|
|
21
43
|
OrderBy: 'StartedAt DESC',
|
|
22
44
|
Fields: ['ID', 'CompanyIntegrationID', 'StartedAt', 'EndedAt', 'TotalRecords',
|
|
23
45
|
'Status', 'ErrorLog', 'Integration', 'Company', 'RunByUser'],
|
|
24
46
|
ResultType: 'simple'
|
|
25
|
-
}
|
|
47
|
+
})
|
|
26
48
|
]);
|
|
27
|
-
const integrations =
|
|
49
|
+
const integrations = engine.CompanyIntegrations;
|
|
28
50
|
const runs = runsResult.Results;
|
|
29
|
-
|
|
51
|
+
const sourceTypes = engine.SourceTypes;
|
|
52
|
+
return integrations.map(integration => this.buildSummary(integration, runs, sourceTypes));
|
|
30
53
|
}
|
|
31
|
-
async LoadEntityMaps(companyIntegrationID,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
EntityName: 'MJ: Company Integration Entity Maps',
|
|
35
|
-
ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}'`,
|
|
36
|
-
OrderBy: 'Priority, ExternalObjectName',
|
|
37
|
-
Fields: ['ID', 'CompanyIntegrationID', 'ExternalObjectName', 'ExternalObjectLabel',
|
|
38
|
-
'EntityID', 'SyncDirection', 'SyncEnabled', 'MatchStrategy',
|
|
39
|
-
'ConflictResolution', 'Priority', 'DeleteBehavior', 'Status', 'Entity'],
|
|
40
|
-
ResultType: 'simple'
|
|
41
|
-
});
|
|
42
|
-
return result.Results;
|
|
54
|
+
async LoadEntityMaps(companyIntegrationID, _provider) {
|
|
55
|
+
await IntegrationEngineBase.Instance.Config(false);
|
|
56
|
+
return IntegrationEngineBase.Instance.GetEntityMapsForCompanyIntegration(companyIntegrationID);
|
|
43
57
|
}
|
|
44
|
-
async LoadFieldMaps(entityMapID,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
EntityName: 'MJ: Company Integration Field Maps',
|
|
48
|
-
ExtraFilter: `EntityMapID='${entityMapID}'`,
|
|
49
|
-
OrderBy: 'Priority, SourceFieldName',
|
|
50
|
-
Fields: ['ID', 'EntityMapID', 'SourceFieldName', 'SourceFieldLabel',
|
|
51
|
-
'DestinationFieldName', 'DestinationFieldLabel', 'Direction',
|
|
52
|
-
'TransformPipeline', 'IsKeyField', 'IsRequired', 'DefaultValue',
|
|
53
|
-
'Priority', 'Status'],
|
|
54
|
-
ResultType: 'simple'
|
|
55
|
-
});
|
|
56
|
-
return result.Results;
|
|
58
|
+
async LoadFieldMaps(entityMapID, _provider) {
|
|
59
|
+
await IntegrationEngineBase.Instance.Config(false);
|
|
60
|
+
return IntegrationEngineBase.Instance.GetFieldMapsForEntityMap(entityMapID);
|
|
57
61
|
}
|
|
58
62
|
async LoadRunHistory(companyIntegrationID, limit = 10, provider) {
|
|
59
63
|
const rv = this.createRunView(provider);
|
|
@@ -74,36 +78,60 @@ export class IntegrationDataService {
|
|
|
74
78
|
EntityName: 'MJ: Company Integration Run Details',
|
|
75
79
|
ExtraFilter: `CompanyIntegrationRunID='${runID}'`,
|
|
76
80
|
OrderBy: 'Entity',
|
|
77
|
-
Fields: ['ID', 'CompanyIntegrationRunID', 'EntityID', '
|
|
78
|
-
'
|
|
79
|
-
'RecordsErrored', 'RecordsSkipped', 'Entity'],
|
|
81
|
+
Fields: ['ID', 'CompanyIntegrationRunID', 'EntityID', 'RecordID',
|
|
82
|
+
'Action', 'IsSuccess', 'Entity'],
|
|
80
83
|
ResultType: 'simple'
|
|
81
84
|
});
|
|
82
|
-
return result.Results;
|
|
85
|
+
return this.aggregateRunDetails(result.Results);
|
|
83
86
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
/** Aggregate raw per-record detail rows into per-entity summary rows */
|
|
88
|
+
aggregateRunDetails(rawRecords) {
|
|
89
|
+
const entityMap = new Map();
|
|
90
|
+
for (const rec of rawRecords) {
|
|
91
|
+
const key = rec.EntityID.toLowerCase();
|
|
92
|
+
let row = entityMap.get(key);
|
|
93
|
+
if (!row) {
|
|
94
|
+
row = {
|
|
95
|
+
EntityID: rec.EntityID,
|
|
96
|
+
Entity: rec.Entity,
|
|
97
|
+
RecordsProcessed: 0,
|
|
98
|
+
RecordsCreated: 0,
|
|
99
|
+
RecordsUpdated: 0,
|
|
100
|
+
RecordsDeleted: 0,
|
|
101
|
+
RecordsErrored: 0,
|
|
102
|
+
RecordsSkipped: 0
|
|
103
|
+
};
|
|
104
|
+
entityMap.set(key, row);
|
|
105
|
+
}
|
|
106
|
+
row.RecordsProcessed++;
|
|
107
|
+
if (!rec.IsSuccess) {
|
|
108
|
+
row.RecordsErrored++;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const action = (rec.Action ?? '').toUpperCase();
|
|
112
|
+
if (action === 'INSERT' || action === 'CREATE') {
|
|
113
|
+
row.RecordsCreated++;
|
|
114
|
+
}
|
|
115
|
+
else if (action === 'UPDATE') {
|
|
116
|
+
row.RecordsUpdated++;
|
|
117
|
+
}
|
|
118
|
+
else if (action === 'DELETE') {
|
|
119
|
+
row.RecordsDeleted++;
|
|
120
|
+
}
|
|
121
|
+
else if (action === 'SKIP' || action === 'SKIPPED') {
|
|
122
|
+
row.RecordsSkipped++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return Array.from(entityMap.values()).sort((a, b) => a.Entity.localeCompare(b.Entity));
|
|
96
127
|
}
|
|
97
|
-
async
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
ResultType: 'simple'
|
|
105
|
-
});
|
|
106
|
-
return result.Results;
|
|
128
|
+
async LoadIntegrationDefinitions(_provider) {
|
|
129
|
+
await IntegrationEngineBase.Instance.Config(false);
|
|
130
|
+
return IntegrationEngineBase.Instance.Integrations;
|
|
131
|
+
}
|
|
132
|
+
async LoadSourceTypes(_provider) {
|
|
133
|
+
await IntegrationEngineBase.Instance.Config(false);
|
|
134
|
+
return IntegrationEngineBase.Instance.SourceTypes.filter(st => st.Status === 'Active');
|
|
107
135
|
}
|
|
108
136
|
async LoadRecentRuns(limit = 20, provider) {
|
|
109
137
|
const rv = this.createRunView(provider);
|
|
@@ -171,10 +199,293 @@ export class IntegrationDataService {
|
|
|
171
199
|
ComputeRelativeTime(dateStr) {
|
|
172
200
|
return this.computeRelativeTime(dateStr);
|
|
173
201
|
}
|
|
202
|
+
// --- Entity Map CRUD ---
|
|
203
|
+
async CreateEntityMap(params) {
|
|
204
|
+
const md = new Metadata();
|
|
205
|
+
const em = await md.GetEntityObject('MJ: Company Integration Entity Maps');
|
|
206
|
+
em.NewRecord();
|
|
207
|
+
em.CompanyIntegrationID = params.CompanyIntegrationID;
|
|
208
|
+
em.ExternalObjectName = params.ExternalObjectName;
|
|
209
|
+
if (params.ExternalObjectLabel)
|
|
210
|
+
em.ExternalObjectLabel = params.ExternalObjectLabel;
|
|
211
|
+
em.EntityID = params.EntityID;
|
|
212
|
+
em.SyncDirection = params.SyncDirection ?? 'Pull';
|
|
213
|
+
em.ConflictResolution = params.ConflictResolution ?? 'SourceWins';
|
|
214
|
+
em.Priority = params.Priority ?? 0;
|
|
215
|
+
em.DeleteBehavior = params.DeleteBehavior ?? 'SoftDelete';
|
|
216
|
+
em.Status = 'Active';
|
|
217
|
+
em.SyncEnabled = true;
|
|
218
|
+
const saved = await em.Save();
|
|
219
|
+
if (!saved) {
|
|
220
|
+
console.error('[IntegrationDataService] Failed to create entity map:', em.LatestResult?.CompleteMessage);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return em;
|
|
224
|
+
}
|
|
225
|
+
async DeleteEntityMap(entityMapID) {
|
|
226
|
+
const md = new Metadata();
|
|
227
|
+
const em = await md.GetEntityObject('MJ: Company Integration Entity Maps');
|
|
228
|
+
await em.Load(entityMapID);
|
|
229
|
+
return em.Delete();
|
|
230
|
+
}
|
|
231
|
+
async ToggleEntityMapEnabled(entityMapID, enabled) {
|
|
232
|
+
const md = new Metadata();
|
|
233
|
+
const em = await md.GetEntityObject('MJ: Company Integration Entity Maps');
|
|
234
|
+
await em.Load(entityMapID);
|
|
235
|
+
em.SyncEnabled = enabled;
|
|
236
|
+
return em.Save();
|
|
237
|
+
}
|
|
238
|
+
// --- Field Map CRUD ---
|
|
239
|
+
async CreateFieldMap(params) {
|
|
240
|
+
const md = new Metadata();
|
|
241
|
+
const fm = await md.GetEntityObject('MJ: Company Integration Field Maps');
|
|
242
|
+
fm.NewRecord();
|
|
243
|
+
fm.EntityMapID = params.EntityMapID;
|
|
244
|
+
fm.SourceFieldName = params.SourceFieldName;
|
|
245
|
+
if (params.SourceFieldLabel)
|
|
246
|
+
fm.SourceFieldLabel = params.SourceFieldLabel;
|
|
247
|
+
fm.DestinationFieldName = params.DestinationFieldName;
|
|
248
|
+
if (params.DestinationFieldLabel)
|
|
249
|
+
fm.DestinationFieldLabel = params.DestinationFieldLabel;
|
|
250
|
+
fm.IsKeyField = params.IsKeyField ?? false;
|
|
251
|
+
fm.IsRequired = params.IsRequired ?? false;
|
|
252
|
+
fm.Direction = params.Direction ?? 'SourceToDest';
|
|
253
|
+
if (params.TransformPipeline !== undefined)
|
|
254
|
+
fm.TransformPipeline = params.TransformPipeline;
|
|
255
|
+
fm.Status = 'Active';
|
|
256
|
+
fm.Priority = params.Priority ?? 0;
|
|
257
|
+
const saved = await fm.Save();
|
|
258
|
+
if (!saved) {
|
|
259
|
+
console.error('[IntegrationDataService] Failed to create field map:', fm.LatestResult?.CompleteMessage);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
return fm;
|
|
263
|
+
}
|
|
264
|
+
async DeleteFieldMap(fieldMapID) {
|
|
265
|
+
const md = new Metadata();
|
|
266
|
+
const fm = await md.GetEntityObject('MJ: Company Integration Field Maps');
|
|
267
|
+
await fm.Load(fieldMapID);
|
|
268
|
+
return fm.Delete();
|
|
269
|
+
}
|
|
270
|
+
async UpdateFieldMap(fieldMapID, updates) {
|
|
271
|
+
const md = new Metadata();
|
|
272
|
+
const fm = await md.GetEntityObject('MJ: Company Integration Field Maps');
|
|
273
|
+
await fm.Load(fieldMapID);
|
|
274
|
+
if (updates.SourceFieldName !== undefined)
|
|
275
|
+
fm.SourceFieldName = updates.SourceFieldName;
|
|
276
|
+
if (updates.DestinationFieldName !== undefined)
|
|
277
|
+
fm.DestinationFieldName = updates.DestinationFieldName;
|
|
278
|
+
if (updates.IsKeyField !== undefined)
|
|
279
|
+
fm.IsKeyField = updates.IsKeyField;
|
|
280
|
+
if (updates.IsRequired !== undefined)
|
|
281
|
+
fm.IsRequired = updates.IsRequired;
|
|
282
|
+
if (updates.Direction !== undefined)
|
|
283
|
+
fm.Direction = updates.Direction;
|
|
284
|
+
if (updates.Status !== undefined)
|
|
285
|
+
fm.Status = updates.Status;
|
|
286
|
+
if (updates.TransformPipeline !== undefined)
|
|
287
|
+
fm.TransformPipeline = updates.TransformPipeline;
|
|
288
|
+
return fm.Save();
|
|
289
|
+
}
|
|
290
|
+
/** Load available MJ entities for mapping target selection */
|
|
291
|
+
async LoadMJEntities(_provider) {
|
|
292
|
+
const md = new Metadata();
|
|
293
|
+
return md.Entities
|
|
294
|
+
.map(e => ({ ID: e.ID, Name: e.Name }))
|
|
295
|
+
.sort((a, b) => a.Name.localeCompare(b.Name));
|
|
296
|
+
}
|
|
297
|
+
/** Load entity fields for a given entity (for field mapping destination picker) */
|
|
298
|
+
async LoadEntityFields(entityID, _provider) {
|
|
299
|
+
const md = new Metadata();
|
|
300
|
+
const entity = md.Entities.find(e => UUIDsEqual(e.ID, entityID));
|
|
301
|
+
if (!entity)
|
|
302
|
+
return [];
|
|
303
|
+
return entity.Fields
|
|
304
|
+
.sort((a, b) => a.Sequence - b.Sequence)
|
|
305
|
+
.map(f => ({ ID: f.ID, Name: f.Name, Type: f.Type, IsRequired: !f.AllowsNull }));
|
|
306
|
+
}
|
|
307
|
+
// --- Discovery (via GraphQL) ---
|
|
308
|
+
/** Discover external objects available for a company integration */
|
|
309
|
+
async DiscoverObjects(companyIntegrationID) {
|
|
310
|
+
const client = this.getIntegrationClient();
|
|
311
|
+
return client.DiscoverObjects(companyIntegrationID);
|
|
312
|
+
}
|
|
313
|
+
/** Discover fields on a specific external object */
|
|
314
|
+
async DiscoverFields(companyIntegrationID, objectName) {
|
|
315
|
+
const client = this.getIntegrationClient();
|
|
316
|
+
return client.DiscoverFields(companyIntegrationID, objectName);
|
|
317
|
+
}
|
|
318
|
+
/** Generate DDL preview for creating tables from discovered objects */
|
|
319
|
+
async SchemaPreview(companyIntegrationID, objects, platform = 'sqlserver') {
|
|
320
|
+
const client = this.getIntegrationClient();
|
|
321
|
+
return client.SchemaPreview(companyIntegrationID, objects, platform);
|
|
322
|
+
}
|
|
323
|
+
/** Get the connector's default configuration for quick setup */
|
|
324
|
+
async GetDefaultConfig(companyIntegrationID) {
|
|
325
|
+
const client = this.getIntegrationClient();
|
|
326
|
+
return client.GetDefaultConfig(companyIntegrationID);
|
|
327
|
+
}
|
|
328
|
+
/** Test connection to the external system */
|
|
329
|
+
async TestConnection(companyIntegrationID) {
|
|
330
|
+
const client = this.getIntegrationClient();
|
|
331
|
+
return client.TestConnection(companyIntegrationID);
|
|
332
|
+
}
|
|
333
|
+
/** Preview source data from the external system via connector's FetchChanges */
|
|
334
|
+
async PreviewSourceData(companyIntegrationID, objectName, limit = 5) {
|
|
335
|
+
const client = this.getIntegrationClient();
|
|
336
|
+
const result = await client.PreviewData(companyIntegrationID, objectName, limit);
|
|
337
|
+
if (!result.Success || !result.Records) {
|
|
338
|
+
console.warn('[IntegrationDataService] PreviewSourceData failed:', result.Message);
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
return result.Records.map(r => {
|
|
342
|
+
try {
|
|
343
|
+
return JSON.parse(r.Data);
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return { _raw: r.Data };
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
/** Preview destination data by loading a few records from the target entity */
|
|
351
|
+
async PreviewDestinationData(entityID, limit = 5, provider) {
|
|
352
|
+
const md = new Metadata();
|
|
353
|
+
const entityInfo = md.Entities.find(e => UUIDsEqual(e.ID, entityID));
|
|
354
|
+
if (!entityInfo)
|
|
355
|
+
return [];
|
|
356
|
+
// Load first N fields (skip internal __mj fields) to keep preview compact
|
|
357
|
+
const fields = entityInfo.Fields
|
|
358
|
+
.filter(f => !f.Name.startsWith('__mj'))
|
|
359
|
+
.slice(0, 8)
|
|
360
|
+
.map(f => f.Name);
|
|
361
|
+
const rv = this.createRunView(provider);
|
|
362
|
+
const result = await rv.RunView({
|
|
363
|
+
EntityName: entityInfo.Name,
|
|
364
|
+
ExtraFilter: '',
|
|
365
|
+
OrderBy: `${fields[0]} ASC`,
|
|
366
|
+
MaxRows: limit,
|
|
367
|
+
Fields: fields,
|
|
368
|
+
ResultType: 'simple'
|
|
369
|
+
});
|
|
370
|
+
return result.Results;
|
|
371
|
+
}
|
|
372
|
+
/** Get a count of records in the destination MJ entity */
|
|
373
|
+
async GetDestinationRecordCount(entityID, provider) {
|
|
374
|
+
const md = new Metadata();
|
|
375
|
+
const entityInfo = md.Entities.find(e => UUIDsEqual(e.ID, entityID));
|
|
376
|
+
if (!entityInfo)
|
|
377
|
+
return 0;
|
|
378
|
+
const rv = this.createRunView(provider);
|
|
379
|
+
const result = await rv.RunView({
|
|
380
|
+
EntityName: entityInfo.Name,
|
|
381
|
+
ExtraFilter: '',
|
|
382
|
+
Fields: [entityInfo.FirstPrimaryKey?.Name ?? 'ID'],
|
|
383
|
+
ResultType: 'simple'
|
|
384
|
+
});
|
|
385
|
+
return result.TotalRowCount ?? result.Results.length;
|
|
386
|
+
}
|
|
387
|
+
/** Get the most recent run that touched a given entity */
|
|
388
|
+
async GetLastSyncForEntity(companyIntegrationID, entityID, provider) {
|
|
389
|
+
const rv = this.createRunView(provider);
|
|
390
|
+
// Get runs for this integration, most recent first
|
|
391
|
+
const runsResult = await rv.RunView({
|
|
392
|
+
EntityName: 'MJ: Company Integration Runs',
|
|
393
|
+
ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}'`,
|
|
394
|
+
OrderBy: 'StartedAt DESC',
|
|
395
|
+
MaxRows: 5,
|
|
396
|
+
Fields: ['ID', 'CompanyIntegrationID', 'StartedAt', 'EndedAt', 'TotalRecords', 'Status'],
|
|
397
|
+
ResultType: 'simple'
|
|
398
|
+
});
|
|
399
|
+
if (runsResult.Results.length === 0)
|
|
400
|
+
return null;
|
|
401
|
+
// Check each run's details to find one that touched this entity
|
|
402
|
+
for (const run of runsResult.Results) {
|
|
403
|
+
const detailResult = await rv.RunView({
|
|
404
|
+
EntityName: 'MJ: Company Integration Run Details',
|
|
405
|
+
ExtraFilter: `CompanyIntegrationRunID='${run.ID}' AND EntityID='${entityID}'`,
|
|
406
|
+
MaxRows: 1,
|
|
407
|
+
Fields: ['EntityID'],
|
|
408
|
+
ResultType: 'simple'
|
|
409
|
+
});
|
|
410
|
+
if (detailResult.Results.length > 0) {
|
|
411
|
+
return {
|
|
412
|
+
StartedAt: run.StartedAt,
|
|
413
|
+
EndedAt: run.EndedAt,
|
|
414
|
+
Status: run.Status,
|
|
415
|
+
TotalRecords: run.TotalRecords
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// No run touched this entity — return the most recent run anyway as context
|
|
420
|
+
const latest = runsResult.Results[0];
|
|
421
|
+
return {
|
|
422
|
+
StartedAt: latest.StartedAt,
|
|
423
|
+
EndedAt: latest.EndedAt,
|
|
424
|
+
Status: latest.Status,
|
|
425
|
+
TotalRecords: latest.TotalRecords
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
/** Run an integration sync via the "Run Integration Sync" action */
|
|
429
|
+
async RunSync(companyIntegrationID) {
|
|
430
|
+
const actionID = await this.lookupActionID('Run Integration Sync');
|
|
431
|
+
if (!actionID) {
|
|
432
|
+
return {
|
|
433
|
+
Success: false,
|
|
434
|
+
Message: 'Action "Run Integration Sync" not found. Ensure the action is registered.'
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const provider = Metadata.Provider;
|
|
438
|
+
const actionClient = new GraphQLActionClient(provider);
|
|
439
|
+
const result = await actionClient.RunAction(actionID, [
|
|
440
|
+
{ Name: 'CompanyIntegrationID', Value: companyIntegrationID, Type: 'Input' }
|
|
441
|
+
]);
|
|
442
|
+
return { Success: result.Success, Message: result.Message ?? '' };
|
|
443
|
+
}
|
|
444
|
+
/** Load schedule data for all company integrations (includes new scheduling fields) */
|
|
445
|
+
async LoadSchedules(_provider) {
|
|
446
|
+
await IntegrationEngineBase.Instance.Config(false);
|
|
447
|
+
return IntegrationEngineBase.Instance.CompanyIntegrations;
|
|
448
|
+
}
|
|
449
|
+
/** Load sync watermarks for a specific company integration's entity maps */
|
|
450
|
+
async LoadWatermarks(companyIntegrationID, _provider) {
|
|
451
|
+
await IntegrationEngineBase.Instance.Config(false);
|
|
452
|
+
const entityMaps = IntegrationEngineBase.Instance.GetEntityMapsForCompanyIntegration(companyIntegrationID);
|
|
453
|
+
if (entityMaps.length === 0)
|
|
454
|
+
return [];
|
|
455
|
+
const mapIDSet = new Set(entityMaps.map(em => em.ID.toLowerCase()));
|
|
456
|
+
return IntegrationEngineBase.Instance.Watermarks.filter(w => mapIDSet.has(w.EntityMapID.toLowerCase()));
|
|
457
|
+
}
|
|
458
|
+
/** Load entity map count per company integration (used by Overview cards) */
|
|
459
|
+
async LoadEntityMapCounts(_provider) {
|
|
460
|
+
await IntegrationEngineBase.Instance.Config(false);
|
|
461
|
+
const counts = new Map();
|
|
462
|
+
for (const em of IntegrationEngineBase.Instance.EntityMaps) {
|
|
463
|
+
if (em.SyncEnabled) {
|
|
464
|
+
const key = em.CompanyIntegrationID.toLowerCase();
|
|
465
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return counts;
|
|
469
|
+
}
|
|
470
|
+
async lookupActionID(actionName) {
|
|
471
|
+
const rv = new RunView();
|
|
472
|
+
const result = await rv.RunView({
|
|
473
|
+
EntityName: 'MJ: Actions',
|
|
474
|
+
ExtraFilter: `Name='${actionName}'`,
|
|
475
|
+
Fields: ['ID'],
|
|
476
|
+
MaxRows: 1,
|
|
477
|
+
ResultType: 'simple'
|
|
478
|
+
});
|
|
479
|
+
return result.Results.length > 0 ? result.Results[0].ID : null;
|
|
480
|
+
}
|
|
481
|
+
getIntegrationClient() {
|
|
482
|
+
const provider = Metadata.Provider;
|
|
483
|
+
return new GraphQLIntegrationClient(provider);
|
|
484
|
+
}
|
|
174
485
|
createRunView(provider) {
|
|
175
486
|
return new RunView(provider ?? null);
|
|
176
487
|
}
|
|
177
|
-
buildSummary(integration, allRuns) {
|
|
488
|
+
buildSummary(integration, allRuns, sourceTypes) {
|
|
178
489
|
const integrationRuns = allRuns.filter(r => UUIDsEqual(r.CompanyIntegrationID, integration.ID));
|
|
179
490
|
const latestRun = integrationRuns.length > 0 ? integrationRuns[0] : null;
|
|
180
491
|
const recentRuns = integrationRuns.slice(0, 5);
|
|
@@ -183,9 +494,10 @@ export class IntegrationDataService {
|
|
|
183
494
|
const totalRecordsSyncedToday = this.computeRecordsSyncedToday(integrationRuns);
|
|
184
495
|
const totalErrors = integrationRuns.filter(r => r.Status === 'Failed').length;
|
|
185
496
|
const durationMs = this.computeDuration(latestRun);
|
|
497
|
+
const sourceType = this.resolveSourceType(integration, sourceTypes);
|
|
186
498
|
return {
|
|
187
499
|
Integration: integration,
|
|
188
|
-
SourceType:
|
|
500
|
+
SourceType: sourceType,
|
|
189
501
|
LatestRun: latestRun,
|
|
190
502
|
RecentRuns: recentRuns,
|
|
191
503
|
StatusColor: statusColor,
|
|
@@ -195,6 +507,11 @@ export class IntegrationDataService {
|
|
|
195
507
|
DurationMs: durationMs
|
|
196
508
|
};
|
|
197
509
|
}
|
|
510
|
+
resolveSourceType(integration, sourceTypes) {
|
|
511
|
+
if (!integration.SourceTypeID)
|
|
512
|
+
return null;
|
|
513
|
+
return sourceTypes.find(st => UUIDsEqual(st.ID, integration.SourceTypeID)) ?? null;
|
|
514
|
+
}
|
|
198
515
|
buildActivityFeedItem(run) {
|
|
199
516
|
return {
|
|
200
517
|
RunID: run.ID,
|