@finos/legend-application-marketplace
Version:
Legend Marketplace application core
398 lines • 20.9 kB
JavaScript
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ActionState, assertErrorThrown, guaranteeNonNullable, isNonNullable, } from '@finos/legend-shared';
import { deserialize } from 'serializr';
import { V1_deserializeDataContractResponse, V1_entitlementsDataProductDetailsResponseToDataProductDetails, V1_liteDataContractWithUserStatusModelSchema, V1_pendingTasksResponseModelSchema, V1_TaskStatusChangeResponseModelSchema, V1_transformDataContractToLiteDatacontract, V1_deserializeDataRequestsWithWorkflowResponse, } from '@finos/legend-graph';
import { buildSyncErrorLayer, buildContractErrorsRoot, getContractAPGCoordinates, getUnverifiedIngestDefinitionsForAPG, } from '@finos/legend-extension-dsl-data-product';
import { makeObservable, flow, observable, flowResult, action, computed, } from 'mobx';
import { TEST_USER, } from './LakehouseEntitlementsStore.js';
export class ContractCreatedByUserDetails {
contractResultLite;
assignees = new Set();
members = new Map();
constructor(contract) {
this.contractResultLite = contract;
makeObservable(this, {
assignees: observable,
members: observable,
sortedAssigneeIds: computed,
sortedMemberIds: computed,
addAssignees: action,
addMember: action,
});
}
get sortedAssigneeIds() {
return Array.from(this.assignees).toSorted();
}
get sortedMemberIds() {
return Array.from(this.members.keys()).toSorted();
}
addAssignees(assignees) {
assignees.forEach((assignee) => this.assignees.add(assignee));
}
addMember(id, status) {
this.members.set(id, status);
}
}
export class EntitlementsDashboardState {
lakehouseEntitlementsStore;
pendingTasks;
pendingTaskContractMap = new Map();
allContractsForUser;
// The contracts createdBy user API returns an entry for each task, not just for each contract.
// To consolidate this information, we store a map of contract ID to the contract details + the
// consolidated user information from the tasks.
allContractsCreatedByUserMap = new Map();
dataRequestsCreatedByUser;
selectedTaskIds = new Set();
initializationState = ActionState.create();
fetchingPendingTasksState = ActionState.create();
fetchingContractsForUserState = ActionState.create();
fetchingContractsByUserState = ActionState.create();
fetchingDataRequestsCreatedByUserState = ActionState.create();
changingState = ActionState.create();
constructor(state) {
this.lakehouseEntitlementsStore = state;
makeObservable(this, {
pendingTasks: observable,
allContractsForUser: observable,
allContractsCreatedByUserMap: observable,
pendingTaskContractMap: observable,
selectedTaskIds: observable,
dataRequestsCreatedByUser: observable,
pendingTaskContracts: computed,
allContractsCreatedByUser: computed,
setSelectedTaskIds: action,
init: flow,
approve: flow,
deny: flow,
fetchPendingTasks: flow,
fetchPendingTaskContracts: flow,
fetchContractsForUser: flow,
fetchContractsCreatedByUser: flow,
fetchContractDeploymentEnvironments: flow,
updateContract: flow,
fetchDataRequestsCreatedByUser: flow,
});
}
get pendingTaskContracts() {
return Array.from(this.pendingTaskContractMap.values());
}
get allContractsCreatedByUser() {
return Array.from(this.allContractsCreatedByUserMap.values());
}
setSelectedTaskIds(ids) {
this.selectedTaskIds = ids;
}
*init(token) {
this.initializationState.inProgress();
this.setSelectedTaskIds(new Set());
try {
this.fetchingPendingTasksState.inProgress();
this.fetchingContractsForUserState.inProgress();
this.fetchingContractsByUserState.inProgress();
this.fetchingDataRequestsCreatedByUserState.inProgress();
const [pendingTasksData, contractsForUser, contractsCreatedByUserMap, dataRequestsCreatedByUser,] = (yield Promise.all([
(async () => {
try {
const tasks = await flowResult(this.fetchPendingTasks(token));
const taskContractMap = await flowResult(this.fetchPendingTaskContracts(token, tasks));
return { tasks, taskContractMap };
}
catch (error) {
assertErrorThrown(error);
this.lakehouseEntitlementsStore.applicationStore.alertUnhandledError(error);
return {
tasks: [],
taskContractMap: new Map(),
};
}
})(),
flowResult(this.fetchContractsForUser(token)),
flowResult(this.fetchContractsCreatedByUser(token)),
flowResult(this.fetchDataRequestsCreatedByUser(token)),
]));
const allContracts = [
...Array.from(pendingTasksData.taskContractMap.values()),
...contractsForUser.map((c) => c.contractResultLite),
...Array.from(contractsCreatedByUserMap.values()).map((c) => c.contractResultLite),
];
const envMap = (yield flowResult(this.fetchContractDeploymentEnvironments(allContracts, token)));
const { filteredTasks, filteredContractsForUser, filteredCreatedByUserMap, filteredDataRequests, } = this.filterByUserEnvironment(pendingTasksData, contractsForUser, contractsCreatedByUserMap, dataRequestsCreatedByUser, envMap);
this.pendingTaskContractMap = pendingTasksData.taskContractMap;
this.pendingTasks = filteredTasks;
this.allContractsForUser = filteredContractsForUser;
this.allContractsCreatedByUserMap = filteredCreatedByUserMap;
this.dataRequestsCreatedByUser = filteredDataRequests;
this.fetchingPendingTasksState.complete();
this.fetchingContractsForUserState.complete();
this.fetchingContractsByUserState.complete();
this.fetchingDataRequestsCreatedByUserState.complete();
}
catch (error) {
assertErrorThrown(error);
this.lakehouseEntitlementsStore.applicationStore.alertUnhandledError(error);
}
finally {
this.initializationState.complete();
}
}
*fetchPendingTasks(token) {
try {
const rawTasks = (yield this.lakehouseEntitlementsStore.marketplaceBaseStore.pendingTasksCache.fetch(TEST_USER, token));
const tasks = deserialize(V1_pendingTasksResponseModelSchema, rawTasks);
return [...tasks.dataOwner, ...tasks.privilegeManager];
}
catch (error) {
assertErrorThrown(error);
this.lakehouseEntitlementsStore.applicationStore.notificationService.notifyError(`Error fetching pending tasks: ${error.message}`);
return [];
}
}
*fetchPendingTaskContracts(token, pendingTasks) {
const pendingTaskContractIds = Array.from(new Set(pendingTasks.map((t) => t.dataContractId)));
const contractClient = this.lakehouseEntitlementsStore.lakehouseContractServerClient;
const plugins = this.lakehouseEntitlementsStore.applicationStore.pluginManager.getPureProtocolProcessorPlugins();
const pendingTaskContracts = (yield Promise.all(pendingTaskContractIds.map(async (contractId) => {
try {
const rawContractResponse = await contractClient.getDataContract(contractId, false, token);
const dataContract = V1_deserializeDataContractResponse(rawContractResponse, plugins)[0]?.dataContract;
if (!dataContract) {
return undefined;
}
return V1_transformDataContractToLiteDatacontract(dataContract);
}
catch (error) {
assertErrorThrown(error);
return undefined;
}
})));
const resultMap = new Map();
pendingTaskContractIds.forEach((contractId, idx) => {
const contract = pendingTaskContracts[idx];
if (contract) {
resultMap.set(contractId, contract);
}
});
return resultMap;
}
*fetchContractsForUser(token) {
try {
const rawContracts = (yield this.lakehouseEntitlementsStore.lakehouseContractServerClient.getContractsForUser(this.lakehouseEntitlementsStore.applicationStore.identityService
.currentUser, token));
return rawContracts.map((rawContract) => deserialize(V1_liteDataContractWithUserStatusModelSchema(this.lakehouseEntitlementsStore.applicationStore.pluginManager.getPureProtocolProcessorPlugins()), rawContract));
}
catch (error) {
assertErrorThrown(error);
this.lakehouseEntitlementsStore.applicationStore.notificationService.notifyError(`Error fetching data contracts for user: ${error.message}`);
return [];
}
}
*fetchContractsCreatedByUser(token) {
try {
const rawContracts = (yield this.lakehouseEntitlementsStore.lakehouseContractServerClient.getContractsCreatedByUser(this.lakehouseEntitlementsStore.applicationStore.identityService
.currentUser, token));
const contracts = rawContracts.map((rawContract) => deserialize(V1_liteDataContractWithUserStatusModelSchema(this.lakehouseEntitlementsStore.applicationStore.pluginManager.getPureProtocolProcessorPlugins()), rawContract));
const resultMap = new Map();
contracts.forEach((contract) => {
if (!resultMap.has(contract.contractResultLite.guid)) {
resultMap.set(contract.contractResultLite.guid, new ContractCreatedByUserDetails(contract.contractResultLite));
}
const entry = guaranteeNonNullable(resultMap.get(contract.contractResultLite.guid));
entry.addAssignees(contract.pendingTaskWithAssignees?.assignees ?? []);
entry.addMember(contract.user, contract.status);
});
return resultMap;
}
catch (error) {
assertErrorThrown(error);
this.lakehouseEntitlementsStore.applicationStore.notificationService.notifyError(`Error fetching data contracts created by user: ${error.message}`);
return new Map();
}
}
*fetchDataRequestsCreatedByUser(token) {
try {
const raw = (yield this.lakehouseEntitlementsStore.lakehouseContractServerClient.getDataAccessRequestsCreatedBy(this.lakehouseEntitlementsStore.applicationStore.identityService
.currentUser, token));
return V1_deserializeDataRequestsWithWorkflowResponse(raw, this.lakehouseEntitlementsStore.applicationStore.pluginManager.getPureProtocolProcessorPlugins());
}
catch (error) {
assertErrorThrown(error);
this.lakehouseEntitlementsStore.applicationStore.notificationService.notifyError(`Error fetching data requests created by user: ${error.message}`);
return [];
}
}
*fetchContractDeploymentEnvironments(allContracts, token) {
const uniqueDIDToDataProduct = new Map();
for (const contract of allContracts) {
uniqueDIDToDataProduct.set(contract.deploymentId, contract.resourceId);
}
const didToEnvType = new Map();
const contractClient = this.lakehouseEntitlementsStore.lakehouseContractServerClient;
yield Promise.all(Array.from(uniqueDIDToDataProduct.entries()).map(async ([deploymentId, resourceId]) => {
try {
const raw = await contractClient.getDataProductByIdAndDID(resourceId, deploymentId, token);
const env = V1_entitlementsDataProductDetailsResponseToDataProductDetails(raw)[0]?.lakehouseEnvironment?.type;
if (env) {
didToEnvType.set(deploymentId, env);
}
}
catch (error) {
assertErrorThrown(error);
}
}));
return didToEnvType;
}
async getContractIngestErrors(contractId, token) {
const baseStore = this.lakehouseEntitlementsStore.marketplaceBaseStore;
const plugins = this.lakehouseEntitlementsStore.applicationStore.pluginManager.getPureProtocolProcessorPlugins();
const apg = await getContractAPGCoordinates(contractId, baseStore.lakehouseContractServerClient, plugins, token);
if (!apg) {
return undefined;
}
const unverifiedIngestDefinitions = await getUnverifiedIngestDefinitionsForAPG(apg, baseStore, plugins, () => baseStore.createInitializedGraphManager(), token);
if (unverifiedIngestDefinitions.length === 0) {
return undefined;
}
return {
title: `Ingest${unverifiedIngestDefinitions.length === 1 ? '' : 's'} Not Found:`,
errorItems: unverifiedIngestDefinitions,
};
}
async getContractSyncErrors(contractId, token) {
try {
const response = (await this.lakehouseEntitlementsStore.lakehouseContractServerClient.getContractSyncStatus(contractId, token));
return buildSyncErrorLayer(response);
}
catch (error) {
assertErrorThrown(error);
return undefined;
}
}
async getContractErrors(contractId, token, checkSyncStatus = false) {
const [ingestErrorsLayer, syncErrorsLayer] = await Promise.all([
this.getContractIngestErrors(contractId, token),
checkSyncStatus
? this.getContractSyncErrors(contractId, token)
: Promise.resolve(undefined),
]);
return buildContractErrorsRoot([ingestErrorsLayer, syncErrorsLayer]);
}
filterByUserEnvironment(pendingData, contractsForUser, contractsCreatedByUserMap, dataRequests, envMap) {
const userEnv = this.lakehouseEntitlementsStore.marketplaceBaseStore.envState
.lakehouseEnvironment;
const envMatchesForDeploymentId = (deploymentId) => {
const env = envMap.get(deploymentId);
return !env || env === userEnv;
};
const filteredTasks = pendingData.tasks.filter((task) => {
const contract = pendingData.taskContractMap.get(task.dataContractId);
return !contract || envMatchesForDeploymentId(contract.deploymentId);
});
const filteredContractsForUser = contractsForUser.filter((c) => envMatchesForDeploymentId(c.contractResultLite.deploymentId));
const filteredCreatedByUserMap = new Map();
for (const [guid, details] of contractsCreatedByUserMap.entries()) {
if (envMatchesForDeploymentId(details.contractResultLite.deploymentId)) {
filteredCreatedByUserMap.set(guid, details);
}
}
const filteredDataRequests = dataRequests.filter((dr) => {
const envType = dr.dataRequest.resourceEnvType;
return !envType || envType === userEnv;
});
return {
filteredTasks,
filteredContractsForUser,
filteredCreatedByUserMap,
filteredDataRequests,
};
}
*updateContract(contractId, token) {
const [newUserContracts, newCreatedByUserContracts] = (yield Promise.all([
(async () => {
const rawContracts = await this.lakehouseEntitlementsStore.lakehouseContractServerClient.getContractsForUser(this.lakehouseEntitlementsStore.applicationStore.identityService
.currentUser, token);
return rawContracts.map((rawContract) => deserialize(V1_liteDataContractWithUserStatusModelSchema(this.lakehouseEntitlementsStore.applicationStore.pluginManager.getPureProtocolProcessorPlugins()), rawContract));
})(),
(async () => {
const rawContracts = await this.lakehouseEntitlementsStore.lakehouseContractServerClient.getContractsCreatedByUser(this.lakehouseEntitlementsStore.applicationStore.identityService
.currentUser, token);
return rawContracts.map((rawContract) => deserialize(V1_liteDataContractWithUserStatusModelSchema(this.lakehouseEntitlementsStore.applicationStore.pluginManager.getPureProtocolProcessorPlugins()), rawContract));
})(),
]));
// Update the contract for the user
this.allContractsForUser = this.allContractsForUser
?.map((contract) => contract.contractResultLite.guid === contractId
? newUserContracts.find((c) => c.contractResultLite.guid === contractId)
: contract)
.filter(isNonNullable);
// Update the contract + all related data for contract created by the user
this.allContractsCreatedByUserMap.delete(contractId);
const updatedCreatedByUserContracts = newCreatedByUserContracts.filter((c) => c.contractResultLite.guid === contractId);
updatedCreatedByUserContracts.forEach((contract) => {
if (!this.allContractsCreatedByUserMap.has(contract.contractResultLite.guid)) {
this.allContractsCreatedByUserMap.set(contract.contractResultLite.guid, new ContractCreatedByUserDetails(contract.contractResultLite));
}
const entry = guaranteeNonNullable(this.allContractsCreatedByUserMap.get(contract.contractResultLite.guid));
entry.addAssignees(contract.pendingTaskWithAssignees?.assignees ?? []);
entry.addMember(contract.user, contract.status);
});
}
*approve(task, token) {
try {
this.changingState.inProgress();
this.changingState.setMessage('Approving Task');
const response = (yield this.lakehouseEntitlementsStore.lakehouseContractServerClient.approveTask(task.taskId, token));
const change = deserialize(V1_TaskStatusChangeResponseModelSchema, response);
if (change.errorMessage) {
throw new Error(`Unable to approve task: ${task.taskId}: ${change.errorMessage}`);
}
task.status = change.status;
this.pendingTasks = [...(this.pendingTasks ?? [])];
this.lakehouseEntitlementsStore.marketplaceBaseStore.pendingTasksCache.invalidate();
this.lakehouseEntitlementsStore.applicationStore.notificationService.notifySuccess(`Task has been Approved`);
}
finally {
this.changingState.complete();
this.changingState.setMessage(undefined);
}
}
*deny(task, token) {
try {
this.changingState.inProgress();
this.lakehouseEntitlementsStore.applicationStore.alertService.setBlockingAlert({
message: 'Denying Task',
prompt: 'Denying task...',
showLoading: true,
});
const response = (yield this.lakehouseEntitlementsStore.lakehouseContractServerClient.denyTask(task.taskId, token));
const change = deserialize(V1_TaskStatusChangeResponseModelSchema, response);
if (change.errorMessage) {
throw new Error(`Unable to deny task: ${task.taskId}: ${change.errorMessage}`);
}
task.status = change.status;
this.pendingTasks = [...(this.pendingTasks ?? [])];
this.lakehouseEntitlementsStore.marketplaceBaseStore.pendingTasksCache.invalidate();
this.lakehouseEntitlementsStore.applicationStore.notificationService.notifySuccess(`Task has been denied`);
}
finally {
this.changingState.complete();
this.changingState.setMessage(undefined);
this.lakehouseEntitlementsStore.applicationStore.alertService.setBlockingAlert(undefined);
}
}
}
//# sourceMappingURL=EntitlementsDashboardState.js.map