@finos/legend-application-marketplace
Version:
Legend Marketplace application core
246 lines (226 loc) • 7.76 kB
text/typescript
/**
* 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 {
type V1_AccessPointGroup,
type V1_DataContract,
type V1_DataProduct,
type V1_DataSubscription,
type V1_DataSubscriptionResponse,
type V1_DataSubscriptionTarget,
V1_ContractState,
V1_CreateSubscriptionInput,
V1_CreateSubscriptionInputModelSchema,
V1_dataSubscriptionModelSchema,
V1_DataSubscriptionResponseModelSchema,
} from '@finos/legend-graph';
import type { DataProductViewerState } from './DataProductViewerState.js';
import {
ActionState,
assertErrorThrown,
guaranteeNonNullable,
uuid,
type GeneratorFn,
type PlainObject,
} from '@finos/legend-shared';
import {
action,
flow,
makeAutoObservable,
makeObservable,
observable,
} from 'mobx';
import {
dataContractContainsAccessGroup,
isContractCompleted,
isMemberOfContract,
} from './LakehouseUtils.js';
import { deserialize, serialize } from 'serializr';
export enum DataProductGroupAccess {
// can be used to indicate fetching or resyncing of group access
UNKNOWN = 'UNKNOWN',
PENDING_MANAGER_APPROVAL = 'PENDING_MANAGER_APPROVAL',
PENDING_DATA_OWNER_APPROVAL = 'PENDING_DATA_OWNER_APPROVAL',
COMPLETED = 'COMPLETED',
NO_ACCESS = 'NO_ACCESS',
}
const getDataProductGroupAccessFromContract = (
val: V1_DataContract,
): DataProductGroupAccess => {
if (isContractCompleted(val)) {
return DataProductGroupAccess.COMPLETED;
} else if (
val.state === V1_ContractState.OPEN_FOR_PRIVILEGE_MANAGER_APPROVAL
) {
return DataProductGroupAccess.PENDING_MANAGER_APPROVAL;
} else if (val.state === V1_ContractState.PENDING_DATA_OWNER_APPROVAL) {
return DataProductGroupAccess.PENDING_DATA_OWNER_APPROVAL;
}
return DataProductGroupAccess.UNKNOWN;
};
export class DataProductGroupAccessState {
readonly accessState: DataProductDataAccessState;
readonly group: V1_AccessPointGroup;
id = uuid();
subscriptions: V1_DataSubscription[] = [];
fetchingAccessState = ActionState.create();
requestingAccessState = ActionState.create();
fetchingSubscriptionsState = ActionState.create();
creatingSubscriptionState = ActionState.create();
// ASSUMPTION: one contract per user per group;
// false here mentions contracts have not been fetched
associatedContract: V1_DataContract | undefined | false = false;
constructor(
group: V1_AccessPointGroup,
accessState: DataProductDataAccessState,
) {
this.group = group;
this.accessState = accessState;
makeAutoObservable(this, {
handleContractClick: action,
handleDataProductContracts: action,
requestingAccessState: observable,
associatedContract: observable,
setAssociatedContract: action,
subscriptions: observable,
fetchingSubscriptionsState: observable,
creatingSubscriptionState: observable,
createSubscription: flow,
setSubscriptions: action,
});
}
get access(): DataProductGroupAccess {
if (this.associatedContract === false) {
return DataProductGroupAccess.UNKNOWN;
}
if (this.associatedContract) {
return getDataProductGroupAccessFromContract(this.associatedContract);
} else {
return DataProductGroupAccess.NO_ACCESS;
}
}
setAssociatedContract(val: V1_DataContract | undefined): void {
this.associatedContract = val;
}
setSubscriptions(val: V1_DataSubscription[]): void {
this.subscriptions = val;
}
handleDataProductContracts(contracts: V1_DataContract[]): void {
const groupContracts = contracts
.filter((_contract) =>
dataContractContainsAccessGroup(this.group, _contract),
)
.filter((_contract) =>
isMemberOfContract(
this.accessState.viewerState.applicationStore.identityService
.currentUser,
_contract,
),
);
// ASSUMPTION: one contract per user per group
const userContract = groupContracts[0];
this.setAssociatedContract(userContract);
}
handleContractClick(): void {
if (this.access === DataProductGroupAccess.NO_ACCESS) {
this.accessState.viewerState.setDataContractAccessPointGroup(this.group);
} else if (
this.access === DataProductGroupAccess.PENDING_MANAGER_APPROVAL ||
this.access === DataProductGroupAccess.PENDING_DATA_OWNER_APPROVAL ||
this.access === DataProductGroupAccess.COMPLETED
) {
const associatedContract = this.associatedContract;
if (associatedContract) {
this.accessState.viewerState.setDataContract(associatedContract);
}
}
}
*fetchSubscriptions(
contractId: string,
token: string | undefined,
): GeneratorFn<void> {
try {
this.fetchingSubscriptionsState.inProgress();
const rawSubscriptions =
(yield this.accessState.viewerState.lakeServerClient.getSubscriptionsForContract(
contractId,
token,
)) as V1_DataSubscriptionResponse;
const subscriptions = rawSubscriptions.subscriptions?.map(
(rawSubscription) =>
deserialize(V1_dataSubscriptionModelSchema, rawSubscription),
);
this.setSubscriptions(subscriptions ?? []);
} catch (error) {
assertErrorThrown(error);
this.accessState.viewerState.applicationStore.notificationService.notifyError(
`${error.message}`,
);
} finally {
this.fetchingSubscriptionsState.complete();
}
}
*createSubscription(
contractId: string,
target: V1_DataSubscriptionTarget,
token: string | undefined,
): GeneratorFn<void> {
try {
this.creatingSubscriptionState.inProgress();
const input = new V1_CreateSubscriptionInput();
input.contractId = contractId;
input.target = target;
const response =
(yield this.accessState.viewerState.lakeServerClient.createSubscription(
serialize(V1_CreateSubscriptionInputModelSchema, input),
token,
)) as PlainObject<V1_DataSubscriptionResponse>;
guaranteeNonNullable(
deserialize(V1_DataSubscriptionResponseModelSchema, response)
.subscriptions?.[0],
'No subsription returned from server',
);
this.accessState.viewerState.applicationStore.notificationService.notifySuccess(
`Subscription created`,
);
this.fetchSubscriptions(contractId, token);
} catch (error) {
assertErrorThrown(error);
this.accessState.viewerState.applicationStore.notificationService.notifyError(
`${error.message}`,
);
} finally {
this.creatingSubscriptionState.complete();
}
}
}
export class DataProductDataAccessState {
readonly viewerState: DataProductViewerState;
accessGroupStates: DataProductGroupAccessState[];
fetchingDataProductAccessState = ActionState.create();
constructor(viewerState: DataProductViewerState) {
makeObservable(this, {
accessGroupStates: observable,
fetchingDataProductAccessState: observable,
});
this.viewerState = viewerState;
this.accessGroupStates = this.product.accessPointGroups.map(
(e) => new DataProductGroupAccessState(e, this),
);
}
get product(): V1_DataProduct {
return this.viewerState.product;
}
}