@finos/legend-application-marketplace 0.2.26 → 0.2.28

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 (64) hide show
  1. package/lib/__lib__/LegendMarketplaceNavigation.d.ts +4 -0
  2. package/lib/__lib__/LegendMarketplaceNavigation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendMarketplaceNavigation.js +4 -0
  4. package/lib/__lib__/LegendMarketplaceNavigation.js.map +1 -1
  5. package/lib/__lib__/LegendMarketplaceTelemetryHelper.d.ts +2 -2
  6. package/lib/__lib__/LegendMarketplaceTelemetryHelper.d.ts.map +1 -1
  7. package/lib/__lib__/LegendMarketplaceTelemetryHelper.js +2 -2
  8. package/lib/__lib__/LegendMarketplaceTelemetryHelper.js.map +1 -1
  9. package/lib/application/LegendMarketplaceApplicationConfig.d.ts +2 -0
  10. package/lib/application/LegendMarketplaceApplicationConfig.d.ts.map +1 -1
  11. package/lib/application/LegendMarketplaceApplicationConfig.js +4 -0
  12. package/lib/application/LegendMarketplaceApplicationConfig.js.map +1 -1
  13. package/lib/components/__test-utils__/LegendMarketplaceStoreTestUtils.d.ts.map +1 -1
  14. package/lib/components/__test-utils__/LegendMarketplaceStoreTestUtils.js +11 -6
  15. package/lib/components/__test-utils__/LegendMarketplaceStoreTestUtils.js.map +1 -1
  16. package/lib/index.css +2 -2
  17. package/lib/index.css.map +1 -1
  18. package/lib/package.json +1 -1
  19. package/lib/pages/Lakehouse/entitlements/EntitlementsClosedContractsDashboard.d.ts.map +1 -1
  20. package/lib/pages/Lakehouse/entitlements/EntitlementsClosedContractsDashboard.js +10 -7
  21. package/lib/pages/Lakehouse/entitlements/EntitlementsClosedContractsDashboard.js.map +1 -1
  22. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingContractsDashboard.d.ts.map +1 -1
  23. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingContractsDashboard.js +3 -2
  24. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingContractsDashboard.js.map +1 -1
  25. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingTasksDashboard.d.ts +1 -1
  26. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingTasksDashboard.d.ts.map +1 -1
  27. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingTasksDashboard.js +67 -38
  28. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingTasksDashboard.js.map +1 -1
  29. package/lib/stores/LegendMarketplaceBaseStore.d.ts.map +1 -1
  30. package/lib/stores/LegendMarketplaceBaseStore.js +1 -0
  31. package/lib/stores/LegendMarketplaceBaseStore.js.map +1 -1
  32. package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts +1 -2
  33. package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts.map +1 -1
  34. package/lib/stores/ai/LegendMarketplaceAIChatStore.js +12 -8
  35. package/lib/stores/ai/LegendMarketplaceAIChatStore.js.map +1 -1
  36. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.d.ts.map +1 -1
  37. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.js +11 -1
  38. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.js.map +1 -1
  39. package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts +39 -15
  40. package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts.map +1 -1
  41. package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js +324 -39
  42. package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js.map +1 -1
  43. package/lib/utils/EntitlementsUtils.d.ts +2 -2
  44. package/lib/utils/EntitlementsUtils.d.ts.map +1 -1
  45. package/lib/utils/EntitlementsUtils.js +11 -2
  46. package/lib/utils/EntitlementsUtils.js.map +1 -1
  47. package/lib/utils/LakehouseUtils.d.ts +2 -1
  48. package/lib/utils/LakehouseUtils.d.ts.map +1 -1
  49. package/lib/utils/LakehouseUtils.js +31 -2
  50. package/lib/utils/LakehouseUtils.js.map +1 -1
  51. package/package.json +12 -12
  52. package/src/__lib__/LegendMarketplaceNavigation.ts +17 -0
  53. package/src/__lib__/LegendMarketplaceTelemetryHelper.ts +6 -6
  54. package/src/application/LegendMarketplaceApplicationConfig.ts +7 -0
  55. package/src/components/__test-utils__/LegendMarketplaceStoreTestUtils.tsx +20 -8
  56. package/src/pages/Lakehouse/entitlements/EntitlementsClosedContractsDashboard.tsx +13 -5
  57. package/src/pages/Lakehouse/entitlements/EntitlementsPendingContractsDashboard.tsx +5 -1
  58. package/src/pages/Lakehouse/entitlements/EntitlementsPendingTasksDashboard.tsx +306 -299
  59. package/src/stores/LegendMarketplaceBaseStore.ts +1 -0
  60. package/src/stores/ai/LegendMarketplaceAIChatStore.ts +14 -9
  61. package/src/stores/lakehouse/LegendMarketplaceProductViewerStore.ts +38 -0
  62. package/src/stores/lakehouse/entitlements/EntitlementsDashboardState.ts +538 -91
  63. package/src/utils/EntitlementsUtils.tsx +13 -2
  64. package/src/utils/LakehouseUtils.tsx +80 -1
@@ -15,39 +15,60 @@
15
15
  */
16
16
 
17
17
  import {
18
- type GeneratorFn,
19
- type PlainObject,
20
18
  ActionState,
21
19
  assertErrorThrown,
22
20
  guaranteeNonNullable,
21
+ guaranteeType,
22
+ HttpStatus,
23
23
  isNonNullable,
24
+ NetworkClientError,
25
+ type GeneratorFn,
26
+ type PlainObject,
24
27
  } from '@finos/legend-shared';
25
28
  import { deserialize } from 'serializr';
26
29
  import {
30
+ type PureProtocolProcessorPlugin,
31
+ type V1_DataProduct,
27
32
  type V1_EnrichedUserApprovalStatus,
28
- type V1_LiteDataContract,
29
33
  type V1_LiteDataContractWithUserStatus,
30
34
  type V1_PendingTasksResponse,
31
- type V1_TaskStatus,
32
- type V1_ContractUserEventRecord,
33
35
  type V1_TaskStatusChangeResponse,
34
- type V1_DataRequestWithWorkflow,
35
- type V1_DataRequestsWithWorkflowResponse,
36
+ RawLambda,
37
+ V1_DataProductAccessor,
36
38
  V1_deserializeDataContractResponse,
39
+ type V1_DataRequestWithWorkflow,
37
40
  V1_entitlementsDataProductDetailsResponseToDataProductDetails,
41
+ V1_IngestDefinitionAccessor,
42
+ V1_LakehouseAccessPoint,
38
43
  V1_liteDataContractWithUserStatusModelSchema,
39
44
  V1_pendingTasksResponseModelSchema,
45
+ V1_PureGraphManager,
46
+ V1_resolveAccessorsFromRawLambda,
47
+ V1_ResourceType,
48
+ V1_SdlcDeploymentDataProductOrigin,
40
49
  V1_TaskStatusChangeResponseModelSchema,
41
50
  V1_transformDataContractToLiteDatacontract,
51
+ V1_transformDataRequestWithWorkflowToLiteDataAccessRequest,
42
52
  V1_deserializeDataRequestsWithWorkflowResponse,
53
+ type V1_DataRequestsWithWorkflowResponse,
54
+ type V1_PendingDataRequestTaskEntry,
55
+ type V1_PendingDataRequestTasksResponse,
56
+ V1_deserializePendingDataRequestTasksResponse,
57
+ type V1_ContractUserEventRecord,
58
+ V1_DataRequestUserEventRecord,
59
+ type V1_PendingTaskRecord,
60
+ V1_ApprovalType,
61
+ V1_UserApprovalStatus,
62
+ type V1_LiteDataContract,
63
+ type V1_LiteAccessRequest,
64
+ V1_PermitTaskAction,
65
+ type V1_OrganizationalScope,
43
66
  } from '@finos/legend-graph';
67
+ import { DEFAULT_TAB_SIZE } from '@finos/legend-application';
44
68
  import {
69
+ stringifyOrganizationalScope,
45
70
  type ContractErrorLayer,
46
- type LakehouseContractSyncStatusResponse,
47
- buildSyncErrorLayer,
48
- buildContractErrorsRoot,
49
- getContractAPGCoordinates,
50
- getUnverifiedIngestDefinitionsForAPG,
71
+ type DataProductDataAccess_LegendApplicationPlugin_Extension,
51
72
  } from '@finos/legend-extension-dsl-data-product';
52
73
  import {
53
74
  makeObservable,
@@ -58,16 +79,104 @@ import {
58
79
  computed,
59
80
  } from 'mobx';
60
81
  import {
61
- type LakehouseEntitlementsStore,
62
82
  TEST_USER,
83
+ type LakehouseEntitlementsStore,
63
84
  } from './LakehouseEntitlementsStore.js';
85
+ import { getDataProductFromDetails } from '../../../utils/LakehouseUtils.js';
86
+
87
+ export enum ContractSyncStatus {
88
+ NEVER_SYNCED = 'NEVER_SYNCED',
89
+ NOT_FULLY_SYNCED = 'NOT_FULLY_SYNCED',
90
+ }
91
+
92
+ export type LakehouseContractSyncStatusResponse = {
93
+ status: string;
94
+ unsyncedUsers?: { username: string }[];
95
+ unsyncedAccessPoints?: { accessPointName: string }[];
96
+ unsyncedTargetAccounts?: string[];
97
+ };
98
+
99
+ const collectIngestSpecPathsFromOriginDp = (
100
+ rootDataProduct: V1_DataProduct,
101
+ accessPointGroupId: string,
102
+ graphManager: V1_PureGraphManager,
103
+ plugins: PureProtocolProcessorPlugin[],
104
+ ): Set<string> => {
105
+ const dpPath = `${rootDataProduct.package}::${rootDataProduct.name}`;
106
+ const targetApg = rootDataProduct.accessPointGroups.find(
107
+ (apg) => apg.id === accessPointGroupId,
108
+ );
109
+ if (!targetApg) {
110
+ throw new Error(
111
+ `Access point group '${accessPointGroupId}' not found in data product '${dpPath}'`,
112
+ );
113
+ }
114
+ const specs = new Set<string>();
115
+ const visited = new Set<string>();
116
+ const worklist: string[] = [accessPointGroupId];
117
+
118
+ const collectFromApg = (apgId: string): void => {
119
+ const apg = rootDataProduct.accessPointGroups.find((g) => g.id === apgId);
120
+ if (!apg) {
121
+ return;
122
+ }
123
+ for (const accessPoint of apg.accessPoints) {
124
+ if (!(accessPoint instanceof V1_LakehouseAccessPoint)) {
125
+ continue;
126
+ }
127
+ const visitKey = `${dpPath}::${accessPoint.id}`;
128
+ if (visited.has(visitKey)) {
129
+ continue;
130
+ }
131
+ visited.add(visitKey);
132
+
133
+ const rawLambda = new RawLambda(
134
+ accessPoint.func.parameters,
135
+ accessPoint.func.body,
136
+ );
137
+ const accessors =
138
+ V1_resolveAccessorsFromRawLambda(rawLambda, graphManager, plugins) ??
139
+ [];
140
+ for (const accessor of accessors) {
141
+ if (accessor instanceof V1_IngestDefinitionAccessor) {
142
+ const specPath = accessor.path[0];
143
+ if (specPath) {
144
+ specs.add(specPath);
145
+ }
146
+ } else if (accessor instanceof V1_DataProductAccessor) {
147
+ const refDpPath = accessor.path[0];
148
+ const refApId = accessor.path[1];
149
+ if (!refDpPath || !refApId) {
150
+ continue;
151
+ }
152
+ if (refDpPath !== dpPath) {
153
+ continue;
154
+ }
155
+ const refApg = rootDataProduct.accessPointGroups.find((g) =>
156
+ g.accessPoints.some((ap) => ap.id === refApId),
157
+ );
158
+ if (refApg && !worklist.includes(refApg.id)) {
159
+ worklist.push(refApg.id);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ };
165
+
166
+ while (worklist.length > 0) {
167
+ const apgId = guaranteeNonNullable(worklist.shift());
168
+ collectFromApg(apgId);
169
+ }
170
+
171
+ return specs;
172
+ };
64
173
 
65
174
  export class ContractCreatedByUserDetails {
66
- readonly contractResultLite: V1_LiteDataContract;
175
+ readonly contractResultLite: V1_LiteAccessRequest;
67
176
  assignees: Set<string> = new Set();
68
177
  members: Map<string, V1_EnrichedUserApprovalStatus> = new Map();
69
178
 
70
- constructor(contract: V1_LiteDataContract) {
179
+ constructor(contract: V1_LiteAccessRequest) {
71
180
  this.contractResultLite = contract;
72
181
 
73
182
  makeObservable(this, {
@@ -99,8 +208,8 @@ export class ContractCreatedByUserDetails {
99
208
 
100
209
  export class EntitlementsDashboardState {
101
210
  readonly lakehouseEntitlementsStore: LakehouseEntitlementsStore;
102
- pendingTasks: V1_ContractUserEventRecord[] | undefined;
103
- pendingTaskContractMap: Map<string, V1_LiteDataContract> = new Map();
211
+ pendingTasks: V1_PendingTaskRecord[] | undefined;
212
+ pendingTaskContractMap: Map<string, V1_LiteAccessRequest> = new Map();
104
213
  allContractsForUser: V1_LiteDataContractWithUserStatus[] | undefined;
105
214
  // The contracts createdBy user API returns an entry for each task, not just for each contract.
106
215
  // To consolidate this information, we store a map of contract ID to the contract details + the
@@ -109,6 +218,9 @@ export class EntitlementsDashboardState {
109
218
  new Map();
110
219
  dataRequestsCreatedByUser: V1_DataRequestWithWorkflow[] | undefined;
111
220
  selectedTaskIds: Set<string> = new Set();
221
+ pendingDataRequestIds: Set<string> = new Set();
222
+ pendingDataRequestDetailsMap: Map<string, V1_DataRequestWithWorkflow> =
223
+ new Map();
112
224
 
113
225
  readonly initializationState = ActionState.create();
114
226
  readonly fetchingPendingTasksState = ActionState.create();
@@ -127,6 +239,8 @@ export class EntitlementsDashboardState {
127
239
  pendingTaskContractMap: observable,
128
240
  selectedTaskIds: observable,
129
241
  dataRequestsCreatedByUser: observable,
242
+ pendingDataRequestIds: observable,
243
+ pendingDataRequestDetailsMap: observable,
130
244
  pendingTaskContracts: computed,
131
245
  allContractsCreatedByUser: computed,
132
246
  setSelectedTaskIds: action,
@@ -140,10 +254,11 @@ export class EntitlementsDashboardState {
140
254
  fetchContractDeploymentEnvironments: flow,
141
255
  updateContract: flow,
142
256
  fetchDataRequestsCreatedByUser: flow,
257
+ fetchPendingDataRequestTasks: flow,
143
258
  });
144
259
  }
145
260
 
146
- get pendingTaskContracts(): V1_LiteDataContract[] {
261
+ get pendingTaskContracts(): V1_LiteAccessRequest[] {
147
262
  return Array.from(this.pendingTaskContractMap.values());
148
263
  }
149
264
 
@@ -169,6 +284,7 @@ export class EntitlementsDashboardState {
169
284
  contractsForUser,
170
285
  contractsCreatedByUserMap,
171
286
  dataRequestsCreatedByUser,
287
+ pendingDataRequestTasksResponse,
172
288
  ] = (yield Promise.all([
173
289
  (async () => {
174
290
  try {
@@ -183,25 +299,44 @@ export class EntitlementsDashboardState {
183
299
  error,
184
300
  );
185
301
  return {
186
- tasks: [] as V1_ContractUserEventRecord[],
187
- taskContractMap: new Map<string, V1_LiteDataContract>(),
302
+ tasks: [] as V1_PendingTaskRecord[],
303
+ taskContractMap: new Map<string, V1_LiteAccessRequest>(),
188
304
  };
189
305
  }
190
306
  })(),
191
307
  flowResult(this.fetchContractsForUser(token)),
192
308
  flowResult(this.fetchContractsCreatedByUser(token)),
193
309
  flowResult(this.fetchDataRequestsCreatedByUser(token)),
310
+ flowResult(this.fetchPendingDataRequestTasks(token)),
194
311
  ])) as [
195
312
  {
196
- tasks: V1_ContractUserEventRecord[];
197
- taskContractMap: Map<string, V1_LiteDataContract>;
313
+ tasks: V1_PendingTaskRecord[];
314
+ taskContractMap: Map<string, V1_LiteAccessRequest>;
198
315
  },
199
316
  V1_LiteDataContractWithUserStatus[],
200
317
  Map<string, ContractCreatedByUserDetails>,
201
318
  V1_DataRequestWithWorkflow[],
319
+ {
320
+ tasks: V1_PendingTaskRecord[];
321
+ taskContractMap: Map<string, V1_LiteAccessRequest>;
322
+ },
202
323
  ];
203
324
 
204
- const allContracts: V1_LiteDataContract[] = [
325
+ // Merge data request pending tasks into contract pending tasks
326
+ const dataRequestIds = new Set<string>();
327
+ for (const task of pendingDataRequestTasksResponse.tasks) {
328
+ pendingTasksData.tasks.push(task);
329
+ dataRequestIds.add(task.accessRequestId);
330
+ }
331
+ for (const [
332
+ id,
333
+ contract,
334
+ ] of pendingDataRequestTasksResponse.taskContractMap) {
335
+ pendingTasksData.taskContractMap.set(id, contract);
336
+ }
337
+ this.pendingDataRequestIds = dataRequestIds;
338
+
339
+ const allContracts: V1_LiteAccessRequest[] = [
205
340
  ...Array.from(pendingTasksData.taskContractMap.values()),
206
341
  ...contractsForUser.map((c) => c.contractResultLite),
207
342
  ...Array.from(contractsCreatedByUserMap.values()).map(
@@ -264,12 +399,132 @@ export class EntitlementsDashboardState {
264
399
  }
265
400
  }
266
401
 
402
+ *fetchPendingDataRequestTasks(token: string | undefined): GeneratorFn<{
403
+ tasks: V1_DataRequestUserEventRecord[];
404
+ taskContractMap: Map<string, V1_LiteAccessRequest>;
405
+ }> {
406
+ try {
407
+ const plugins =
408
+ this.lakehouseEntitlementsStore.applicationStore.pluginManager.getPureProtocolProcessorPlugins();
409
+ const currentUser =
410
+ this.lakehouseEntitlementsStore.applicationStore.identityService
411
+ .currentUser;
412
+ const rawResponse =
413
+ (yield this.lakehouseEntitlementsStore.lakehouseContractServerClient.getPendingDataRequestTasks(
414
+ currentUser,
415
+ token,
416
+ )) as PlainObject<V1_PendingDataRequestTasksResponse>;
417
+ const response = V1_deserializePendingDataRequestTasksResponse(
418
+ rawResponse,
419
+ plugins,
420
+ );
421
+
422
+ // Collect unique data request IDs and fetch withWorkflow details for each
423
+ const allEntries = [...response.dataOwner, ...response.privilegeManager];
424
+ const uniqueDataRequestIds = Array.from(
425
+ new Set(allEntries.map((e) => e.dataRequestId)),
426
+ );
427
+ const dataRequestDetailsMap = new Map<
428
+ string,
429
+ V1_DataRequestWithWorkflow
430
+ >();
431
+ const contractClient =
432
+ this.lakehouseEntitlementsStore.lakehouseContractServerClient;
433
+ yield Promise.all(
434
+ uniqueDataRequestIds.map(async (dataRequestId) => {
435
+ try {
436
+ const rawDetail =
437
+ await contractClient.getDataAccessRequestWithWorkflow(
438
+ dataRequestId,
439
+ token,
440
+ );
441
+ const details = V1_deserializeDataRequestsWithWorkflowResponse(
442
+ rawDetail,
443
+ plugins,
444
+ );
445
+ if (details[0]) {
446
+ dataRequestDetailsMap.set(dataRequestId, details[0]);
447
+ }
448
+ } catch (error) {
449
+ assertErrorThrown(error);
450
+ }
451
+ }),
452
+ );
453
+ this.pendingDataRequestDetailsMap = dataRequestDetailsMap;
454
+
455
+ const tasks: V1_DataRequestUserEventRecord[] = [];
456
+ const taskContractMap = new Map<string, V1_LiteAccessRequest>();
457
+
458
+ const processEntries = (
459
+ entries: V1_PendingDataRequestTaskEntry[],
460
+ approvalType: V1_ApprovalType,
461
+ ): void => {
462
+ for (const entry of entries) {
463
+ const task = entry.task;
464
+ const detail = dataRequestDetailsMap.get(entry.dataRequestId);
465
+
466
+ // Create V1_DataRequestUserEventRecord for grid row data
467
+ const record = new V1_DataRequestUserEventRecord();
468
+ record.taskId = task.taskId;
469
+ record.dataRequestId = entry.dataRequestId;
470
+ record.status = V1_UserApprovalStatus.PENDING;
471
+ record.consumer = this.getConsumerDisplayString(task.consumer);
472
+ record.type = approvalType;
473
+ tasks.push(record);
474
+
475
+ // Create V1_LiteDataAccessRequest for grid column lookups
476
+ if (detail && !taskContractMap.has(entry.dataRequestId)) {
477
+ taskContractMap.set(
478
+ entry.dataRequestId,
479
+ V1_transformDataRequestWithWorkflowToLiteDataAccessRequest(
480
+ detail,
481
+ ),
482
+ );
483
+ }
484
+ }
485
+ };
486
+
487
+ processEntries(response.dataOwner, V1_ApprovalType.DATA_OWNER_APPROVAL);
488
+ processEntries(
489
+ response.privilegeManager,
490
+ V1_ApprovalType.CONSUMER_PRIVILEGE_MANAGER_APPROVAL,
491
+ );
492
+
493
+ return { tasks, taskContractMap };
494
+ } catch (error) {
495
+ assertErrorThrown(error);
496
+ this.lakehouseEntitlementsStore.applicationStore.notificationService.notifyError(
497
+ `Error fetching pending data request tasks: ${error.message}`,
498
+ );
499
+ return {
500
+ tasks: [],
501
+ taskContractMap: new Map<string, V1_LiteAccessRequest>(),
502
+ };
503
+ }
504
+ }
505
+
506
+ private getConsumerDisplayString(consumer: V1_OrganizationalScope): string {
507
+ const dataAccessPlugins =
508
+ this.lakehouseEntitlementsStore.applicationStore.pluginManager.getApplicationPlugins() as DataProductDataAccess_LegendApplicationPlugin_Extension[];
509
+ const orgNodeCode = dataAccessPlugins
510
+ .map((p) => p.getOrganizationalNodeCode?.(consumer))
511
+ .find(isNonNullable);
512
+ if (orgNodeCode) {
513
+ return orgNodeCode;
514
+ }
515
+ return stringifyOrganizationalScope(consumer, dataAccessPlugins);
516
+ }
517
+
518
+ private static parseDeploymentId(did: string): number {
519
+ return Number.parseInt(did, 10) || 0;
520
+ }
521
+
267
522
  *fetchPendingTaskContracts(
268
523
  token: string | undefined,
269
- pendingTasks: V1_ContractUserEventRecord[],
270
- ): GeneratorFn<Map<string, V1_LiteDataContract>> {
524
+ pendingTasks: V1_PendingTaskRecord[],
525
+ ): GeneratorFn<Map<string, V1_LiteAccessRequest>> {
271
526
  const pendingTaskContractIds = Array.from(
272
- new Set(pendingTasks.map((t) => t.dataContractId)),
527
+ new Set(pendingTasks.map((t) => t.accessRequestId)),
273
528
  );
274
529
  const contractClient =
275
530
  this.lakehouseEntitlementsStore.lakehouseContractServerClient;
@@ -297,7 +552,7 @@ export class EntitlementsDashboardState {
297
552
  }
298
553
  }),
299
554
  )) as (V1_LiteDataContract | undefined)[];
300
- const resultMap = new Map<string, V1_LiteDataContract>();
555
+ const resultMap = new Map<string, V1_LiteAccessRequest>();
301
556
  pendingTaskContractIds.forEach((contractId, idx) => {
302
557
  const contract = pendingTaskContracts[idx];
303
558
  if (contract) {
@@ -400,7 +655,7 @@ export class EntitlementsDashboardState {
400
655
  }
401
656
 
402
657
  *fetchContractDeploymentEnvironments(
403
- allContracts: V1_LiteDataContract[],
658
+ allContracts: V1_LiteAccessRequest[],
404
659
  token: string | undefined,
405
660
  ): GeneratorFn<Map<number, string>> {
406
661
  const uniqueDIDToDataProduct = new Map<number, string>();
@@ -436,37 +691,158 @@ export class EntitlementsDashboardState {
436
691
  return didToEnvType;
437
692
  }
438
693
 
439
- async getContractIngestErrors(
694
+ async getUnverifiedIngestDefinitions(
440
695
  contractId: string,
441
696
  token: string | undefined,
442
- ): Promise<ContractErrorLayer | undefined> {
443
- const baseStore = this.lakehouseEntitlementsStore.marketplaceBaseStore;
697
+ ): Promise<string[]> {
698
+ const entitlementsStore = this.lakehouseEntitlementsStore;
699
+ const baseStore = entitlementsStore.marketplaceBaseStore;
700
+ const applicationStore = entitlementsStore.applicationStore;
444
701
  const plugins =
445
- this.lakehouseEntitlementsStore.applicationStore.pluginManager.getPureProtocolProcessorPlugins();
446
- const apg = await getContractAPGCoordinates(
447
- contractId,
448
- baseStore.lakehouseContractServerClient,
449
- plugins,
450
- token,
451
- );
452
- if (!apg) {
453
- return undefined;
454
- }
455
- const unverifiedIngestDefinitions =
456
- await getUnverifiedIngestDefinitionsForAPG(
457
- apg,
702
+ applicationStore.pluginManager.getPureProtocolProcessorPlugins();
703
+ const contractClient = entitlementsStore.lakehouseContractServerClient;
704
+
705
+ const PROD_ENV = 'prod';
706
+ const SDLC_DEPLOYMENT = 'alloy-git';
707
+
708
+ try {
709
+ const liteContract = await (async () => {
710
+ try {
711
+ const rawContractResponse = await contractClient.getDataContract(
712
+ contractId,
713
+ false,
714
+ token,
715
+ );
716
+ const dataContract = V1_deserializeDataContractResponse(
717
+ rawContractResponse,
718
+ plugins,
719
+ )[0]?.dataContract;
720
+ if (!dataContract) {
721
+ return undefined;
722
+ }
723
+ return V1_transformDataContractToLiteDatacontract(dataContract);
724
+ } catch (error) {
725
+ assertErrorThrown(error);
726
+ return undefined;
727
+ }
728
+ })();
729
+ if (!liteContract) {
730
+ return [];
731
+ }
732
+
733
+ const accessPointGroupId =
734
+ liteContract.resourceType === V1_ResourceType.ACCESS_POINT_GROUP
735
+ ? (liteContract.accessPointGroup ?? undefined)
736
+ : undefined;
737
+ if (!accessPointGroupId) {
738
+ return [];
739
+ }
740
+
741
+ const dpDetails = await (async () => {
742
+ try {
743
+ const raw = await contractClient.getDataProductByIdAndDID(
744
+ liteContract.resourceId,
745
+ liteContract.deploymentId,
746
+ token,
747
+ );
748
+ return V1_entitlementsDataProductDetailsResponseToDataProductDetails(
749
+ raw,
750
+ )[0];
751
+ } catch (error) {
752
+ assertErrorThrown(error);
753
+ return undefined;
754
+ }
755
+ })();
756
+ if (!dpDetails) {
757
+ return [];
758
+ }
759
+
760
+ if (!(dpDetails.origin instanceof V1_SdlcDeploymentDataProductOrigin)) {
761
+ return [];
762
+ }
763
+
764
+ const graphManager = new V1_PureGraphManager(
765
+ applicationStore.pluginManager,
766
+ applicationStore.logService,
767
+ baseStore.remoteEngine,
768
+ );
769
+ await graphManager.initialize(
770
+ {
771
+ env: applicationStore.config.env,
772
+ tabSize: DEFAULT_TAB_SIZE,
773
+ clientConfig: {
774
+ baseUrl: applicationStore.config.engineServerUrl,
775
+ },
776
+ },
777
+ { engine: baseStore.remoteEngine },
778
+ );
779
+
780
+ const v1DataProduct = await getDataProductFromDetails(
781
+ dpDetails,
782
+ graphManager,
458
783
  baseStore,
784
+ );
785
+ if (!v1DataProduct) {
786
+ return [];
787
+ }
788
+
789
+ const specs = collectIngestSpecPathsFromOriginDp(
790
+ v1DataProduct,
791
+ accessPointGroupId,
792
+ graphManager,
459
793
  plugins,
460
- () => baseStore.createInitializedGraphManager(),
461
- token,
462
794
  );
463
- if (unverifiedIngestDefinitions.length === 0) {
464
- return undefined;
795
+ if (specs.size === 0) {
796
+ return [];
797
+ }
798
+
799
+ const ingestEnvironment =
800
+ await baseStore.lakehouseDataProductService.getOrFetchEnvironmentForDID(
801
+ liteContract.deploymentId,
802
+ token,
803
+ );
804
+ const ingestServerUrl = ingestEnvironment?.ingestServerUrl;
805
+ if (ingestServerUrl === undefined) {
806
+ return [];
807
+ }
808
+
809
+ const sdlcOrigin = guaranteeType(
810
+ dpDetails.origin,
811
+ V1_SdlcDeploymentDataProductOrigin,
812
+ );
813
+ const gav = `${sdlcOrigin.group}~${sdlcOrigin.artifact}`;
814
+ const specsToVerify = Array.from(specs, (specPath) => ({
815
+ specPath,
816
+ urn: `urn:lakehouse:${PROD_ENV}:ingest:definition:${SDLC_DEPLOYMENT}:${gav}~${specPath}`,
817
+ }));
818
+
819
+ const ingestClient = baseStore.lakehouseIngestServerClient;
820
+ const settled = await Promise.all(
821
+ specsToVerify.map(async (entry) => {
822
+ try {
823
+ await ingestClient.getIngestDefinitionDetail(
824
+ entry.urn,
825
+ ingestServerUrl,
826
+ token,
827
+ );
828
+ return undefined;
829
+ } catch (error) {
830
+ assertErrorThrown(error);
831
+ if (
832
+ error instanceof NetworkClientError &&
833
+ error.response.status === HttpStatus.NOT_FOUND
834
+ ) {
835
+ return entry.specPath;
836
+ }
837
+ return undefined;
838
+ }
839
+ }),
840
+ );
841
+ return settled.filter(isNonNullable);
842
+ } catch (error) {
843
+ assertErrorThrown(error);
844
+ return [];
465
845
  }
466
- return {
467
- title: `Ingest${unverifiedIngestDefinitions.length === 1 ? '' : 's'} Not Found:`,
468
- errorItems: unverifiedIngestDefinitions,
469
- };
470
846
  }
471
847
 
472
848
  async getContractSyncErrors(
@@ -479,7 +855,39 @@ export class EntitlementsDashboardState {
479
855
  contractId,
480
856
  token,
481
857
  )) as LakehouseContractSyncStatusResponse;
482
- return buildSyncErrorLayer(response);
858
+
859
+ const status = response.status.toUpperCase();
860
+
861
+ if (status === ContractSyncStatus.NEVER_SYNCED) {
862
+ return { title: 'Sync Error: Contract Never Synced' };
863
+ }
864
+
865
+ if (status === ContractSyncStatus.NOT_FULLY_SYNCED) {
866
+ const unsyncedUsers =
867
+ response.unsyncedUsers?.map((user) => user.username) ?? [];
868
+ const unsyncedAccessPoints =
869
+ response.unsyncedAccessPoints?.map(
870
+ (accessPoint) => accessPoint.accessPointName,
871
+ ) ?? [];
872
+ const unsyncedTargetAccounts = response.unsyncedTargetAccounts ?? [];
873
+
874
+ const syncGroupingLayers: ContractErrorLayer[] = [
875
+ { title: 'Users:', errorItems: unsyncedUsers },
876
+ { title: 'Target Accounts:', errorItems: unsyncedTargetAccounts },
877
+ { title: 'Access Points:', errorItems: unsyncedAccessPoints },
878
+ ].filter((layer) => layer.errorItems.length > 0);
879
+
880
+ if (syncGroupingLayers.length === 0) {
881
+ return undefined;
882
+ }
883
+
884
+ return {
885
+ title: 'Unsynced Entities:',
886
+ childLayers: syncGroupingLayers,
887
+ };
888
+ }
889
+
890
+ return undefined;
483
891
  } catch (error) {
484
892
  assertErrorThrown(error);
485
893
  return undefined;
@@ -491,26 +899,39 @@ export class EntitlementsDashboardState {
491
899
  token: string | undefined,
492
900
  checkSyncStatus = false,
493
901
  ): Promise<ContractErrorLayer | undefined> {
494
- const [ingestErrorsLayer, syncErrorsLayer] = await Promise.all([
495
- this.getContractIngestErrors(contractId, token),
902
+ const [unverifiedIngestDefinitions, syncErrorsLayer] = await Promise.all([
903
+ this.getUnverifiedIngestDefinitions(contractId, token),
496
904
  checkSyncStatus
497
905
  ? this.getContractSyncErrors(contractId, token)
498
906
  : Promise.resolve(undefined),
499
907
  ]);
500
- return buildContractErrorsRoot([ingestErrorsLayer, syncErrorsLayer]);
908
+
909
+ const childLayers: ContractErrorLayer[] = [
910
+ unverifiedIngestDefinitions.length > 0
911
+ ? {
912
+ title: `Ingest${unverifiedIngestDefinitions.length === 1 ? '' : 's'} Not Found:`,
913
+ errorItems: unverifiedIngestDefinitions,
914
+ }
915
+ : undefined,
916
+ syncErrorsLayer,
917
+ ].filter(isNonNullable);
918
+
919
+ return childLayers.length > 0
920
+ ? { title: 'Contract Errors:', childLayers }
921
+ : undefined;
501
922
  }
502
923
 
503
924
  filterByUserEnvironment(
504
925
  pendingData: {
505
- tasks: V1_ContractUserEventRecord[];
506
- taskContractMap: Map<string, V1_LiteDataContract>;
926
+ tasks: V1_PendingTaskRecord[];
927
+ taskContractMap: Map<string, V1_LiteAccessRequest>;
507
928
  },
508
929
  contractsForUser: V1_LiteDataContractWithUserStatus[],
509
930
  contractsCreatedByUserMap: Map<string, ContractCreatedByUserDetails>,
510
931
  dataRequests: V1_DataRequestWithWorkflow[],
511
932
  envMap: Map<number, string>,
512
933
  ): {
513
- filteredTasks: V1_ContractUserEventRecord[];
934
+ filteredTasks: V1_PendingTaskRecord[];
514
935
  filteredContractsForUser: V1_LiteDataContractWithUserStatus[];
515
936
  filteredCreatedByUserMap: Map<string, ContractCreatedByUserDetails>;
516
937
  filteredDataRequests: V1_DataRequestWithWorkflow[];
@@ -524,7 +945,7 @@ export class EntitlementsDashboardState {
524
945
  };
525
946
 
526
947
  const filteredTasks = pendingData.tasks.filter((task) => {
527
- const contract = pendingData.taskContractMap.get(task.dataContractId);
948
+ const contract = pendingData.taskContractMap.get(task.accessRequestId);
528
949
  return !contract || envMatchesForDeploymentId(contract.deploymentId);
529
950
  });
530
951
  const filteredContractsForUser = contractsForUser.filter((c) =>
@@ -627,30 +1048,72 @@ export class EntitlementsDashboardState {
627
1048
  });
628
1049
  }
629
1050
 
630
- *approve(
631
- task: V1_ContractUserEventRecord,
1051
+ private getDataRequestWorkflowId(dataRequestId: string): string {
1052
+ const detail = this.pendingDataRequestDetailsMap.get(dataRequestId);
1053
+ const workflowId = detail?.workflows[0]?.workflowId;
1054
+ if (!workflowId) {
1055
+ throw new Error(`No workflow found for data request ${dataRequestId}`);
1056
+ }
1057
+ return workflowId;
1058
+ }
1059
+
1060
+ private *changeTaskStatus(
1061
+ task: V1_PendingTaskRecord,
632
1062
  token: string | undefined,
1063
+ taskAction: 'approve' | 'deny',
633
1064
  ): GeneratorFn<void> {
634
- try {
635
- this.changingState.inProgress();
636
- this.changingState.setMessage('Approving Task');
637
- const response =
638
- (yield this.lakehouseEntitlementsStore.lakehouseContractServerClient.approveTask(
639
- task.taskId,
640
- token,
641
- )) as PlainObject<V1_TaskStatusChangeResponse>;
1065
+ const isApprove = taskAction === 'approve';
1066
+
1067
+ if (task instanceof V1_DataRequestUserEventRecord) {
1068
+ const workflowId = this.getDataRequestWorkflowId(task.dataRequestId);
1069
+ const permitClient =
1070
+ this.lakehouseEntitlementsStore.marketplaceBaseStore
1071
+ .permitWorkflowServerClient;
1072
+ if (!permitClient) {
1073
+ throw new Error('Permit workflow client is not configured');
1074
+ }
1075
+ yield permitClient.performTaskAction(
1076
+ workflowId,
1077
+ task.taskId,
1078
+ isApprove ? V1_PermitTaskAction.APPROVE : V1_PermitTaskAction.REJECT,
1079
+ `${isApprove ? 'Approved' : 'Denied'} via entitlements dashboard`,
1080
+ token,
1081
+ );
1082
+ task.status = isApprove
1083
+ ? V1_UserApprovalStatus.APPROVED
1084
+ : V1_UserApprovalStatus.DENIED;
1085
+ } else {
1086
+ const contractClient =
1087
+ this.lakehouseEntitlementsStore.lakehouseContractServerClient;
1088
+ const response = (yield isApprove
1089
+ ? contractClient.approveTask(task.taskId, token)
1090
+ : contractClient.denyTask(
1091
+ task.taskId,
1092
+ token,
1093
+ )) as PlainObject<V1_TaskStatusChangeResponse>;
642
1094
  const change = deserialize(
643
1095
  V1_TaskStatusChangeResponseModelSchema,
644
1096
  response,
645
1097
  );
646
1098
  if (change.errorMessage) {
647
1099
  throw new Error(
648
- `Unable to approve task: ${task.taskId}: ${change.errorMessage}`,
1100
+ `Unable to ${taskAction} task: ${task.taskId}: ${change.errorMessage}`,
649
1101
  );
650
1102
  }
651
1103
  task.status = change.status;
652
- this.pendingTasks = [...(this.pendingTasks ?? [])];
653
- this.lakehouseEntitlementsStore.marketplaceBaseStore.pendingTasksCache.invalidate();
1104
+ }
1105
+ this.pendingTasks = [...(this.pendingTasks ?? [])];
1106
+ this.lakehouseEntitlementsStore.marketplaceBaseStore.pendingTasksCache.invalidate();
1107
+ }
1108
+
1109
+ *approve(
1110
+ task: V1_PendingTaskRecord,
1111
+ token: string | undefined,
1112
+ ): GeneratorFn<void> {
1113
+ try {
1114
+ this.changingState.inProgress();
1115
+ this.changingState.setMessage('Approving Task');
1116
+ yield* this.changeTaskStatus(task, token, 'approve');
654
1117
  this.lakehouseEntitlementsStore.applicationStore.notificationService.notifySuccess(
655
1118
  `Task has been Approved`,
656
1119
  );
@@ -661,7 +1124,7 @@ export class EntitlementsDashboardState {
661
1124
  }
662
1125
 
663
1126
  *deny(
664
- task: V1_ContractUserEventRecord,
1127
+ task: V1_PendingTaskRecord,
665
1128
  token: string | undefined,
666
1129
  ): GeneratorFn<void> {
667
1130
  try {
@@ -673,23 +1136,7 @@ export class EntitlementsDashboardState {
673
1136
  showLoading: true,
674
1137
  },
675
1138
  );
676
- const response =
677
- (yield this.lakehouseEntitlementsStore.lakehouseContractServerClient.denyTask(
678
- task.taskId,
679
- token,
680
- )) as PlainObject<V1_TaskStatus>;
681
- const change = deserialize(
682
- V1_TaskStatusChangeResponseModelSchema,
683
- response,
684
- );
685
- if (change.errorMessage) {
686
- throw new Error(
687
- `Unable to deny task: ${task.taskId}: ${change.errorMessage}`,
688
- );
689
- }
690
- task.status = change.status;
691
- this.pendingTasks = [...(this.pendingTasks ?? [])];
692
- this.lakehouseEntitlementsStore.marketplaceBaseStore.pendingTasksCache.invalidate();
1139
+ yield* this.changeTaskStatus(task, token, 'deny');
693
1140
  this.lakehouseEntitlementsStore.applicationStore.notificationService.notifySuccess(
694
1141
  `Task has been denied`,
695
1142
  );