@finos/legend-application-marketplace
Version:
Legend Marketplace application core
381 lines • 26.4 kB
JavaScript
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
/**
* 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 { GraphManagerState, V1_ApprovalType, V1_ResourceType, } from '@finos/legend-graph';
import { DataGrid, } from '@finos/legend-lego/data-grid';
import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle, Tooltip, } from '@mui/material';
import { useCallback, useMemo, useState } from 'react';
import { useLegendMarketplaceBaseStore } from '../../../application/providers/LegendMarketplaceFrameworkProvider.js';
import { CubesLoadingIndicator, CubesLoadingIndicatorIcon, InfoCircleIcon, } from '@finos/legend-art';
import { flowResult } from 'mobx';
import { useAuth } from 'react-oidc-context';
import { observer } from 'mobx-react-lite';
import { startCase } from '@finos/legend-shared';
import { UserRenderer, getOrganizationalScopeTypeName, getOrganizationalScopeTypeDetails, DataAccessRequestViewer, DataContractViewerState, } from '@finos/legend-extension-dsl-data-product';
import { generateContractPagePath, generateLakehouseDataProductPath, } from '../../../__lib__/LegendMarketplaceNavigation.js';
import { CONTRACT_ACTION, LegendMarketplaceTelemetryHelper, } from '../../../__lib__/LegendMarketplaceTelemetryHelper.js';
import { formatOrderDate } from '../../../stores/orders/OrderHelpers.js';
const EntitlementsDashboardActionModal = (props) => {
const { open, selectedTasks, dashboardState, onClose, action, pendingTaskContracts, marketplaceBaseStore, } = props;
const auth = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [errorMessages, setErrorMessages] = useState([]);
const [successCount, setSuccessCount] = useState(0);
const handleClose = () => {
setIsLoading(false);
setErrorMessages([]);
setSuccessCount(0);
onClose();
};
const actionFunction = action === 'approve'
? dashboardState.approve.bind(dashboardState)
: dashboardState.deny.bind(dashboardState);
const handleAction = async () => {
setIsLoading(true);
const currentErrorMessages = [];
await Promise.all(Array.from(selectedTasks).map(async (task) => {
return flowResult(actionFunction(task, auth.user?.access_token))
.then(() => setSuccessCount((prev) => prev++))
.catch((error) => currentErrorMessages.push([task, error.message]));
}));
setIsLoading(false);
// If everything was successful, show a success message and close the modal.
if (currentErrorMessages.length === 0) {
dashboardState.lakehouseEntitlementsStore.applicationStore.notificationService.notifySuccess(`${selectedTasks.length} selected contract requests have been ${action === 'approve' ? 'approved' : 'denied'} successfully.`);
handleClose();
LegendMarketplaceTelemetryHelper.logEvent_ActionDataContracts(dashboardState.lakehouseEntitlementsStore.applicationStore
.telemetryService, selectedTasks, pendingTaskContracts, action === 'approve'
? CONTRACT_ACTION.APPROVED
: CONTRACT_ACTION.DENIED, dashboardState.lakehouseEntitlementsStore.applicationStore
.identityService.currentUser, undefined);
}
else {
// If there were errors, we won't close the modal and will show the errors in the modal.
setErrorMessages(currentErrorMessages);
LegendMarketplaceTelemetryHelper.logEvent_ActionDataContracts(dashboardState.lakehouseEntitlementsStore.applicationStore
.telemetryService, selectedTasks, pendingTaskContracts, action === 'approve'
? CONTRACT_ACTION.APPROVED
: CONTRACT_ACTION.DENIED, dashboardState.lakehouseEntitlementsStore.applicationStore
.identityService.currentUser, currentErrorMessages.map((error) => error[1]));
}
// Refresh pending tasks and contracts after taking action
await flowResult(dashboardState.init(auth.user?.access_token));
};
if (action === undefined) {
return null;
}
return (_jsxs(Dialog, { open: open, onClose: handleClose, fullWidth: true, maxWidth: "md", children: [_jsxs(DialogTitle, { children: [action === 'approve' ? 'Approve' : 'Deny', " Contract Requests"] }), _jsxs(DialogContent, { className: "marketplace-lakehouse-entitlements__data-contract-approval__content", children: [_jsx(CubesLoadingIndicator, { isLoading: isLoading, children: _jsx(CubesLoadingIndicatorIcon, {}) }), !isLoading && errorMessages.length === 0 && (_jsxs("div", { children: [action === 'approve' ? 'Approve' : 'Deny', " ", selectedTasks.length, ' ', "selected contract requests"] })), !isLoading && errorMessages.length > 0 && (_jsxs(_Fragment, { children: [successCount > 0 && (_jsxs(Box, { className: "marketplace-lakehouse-entitlements__data-contract-approval__success", children: [successCount, " selected contract requests were", ' ', action === 'approve' ? 'approved' : 'denied', " successfully"] })), errorMessages.map(([task, errorMessage]) => {
const contractId = task.dataContractId;
const contract = pendingTaskContracts.find((c) => c.guid === contractId);
return (_jsxs(Box, { className: "marketplace-lakehouse-entitlements__data-contract-approval__error", children: [_jsxs("div", { className: "marketplace-lakehouse-entitlements__data-contract-approval__error__content", children: ["Encountered an error", ' ', action === 'approve' ? 'approving' : 'denying', " request for", ' ', _jsx("div", { className: "marketplace-lakehouse-entitlements__data-contract-approval__error__user", children: _jsx(UserRenderer, { userId: task.consumer, applicationStore: marketplaceBaseStore.applicationStore, userSearchService: marketplaceBaseStore.userSearchService }) }), ' ', "for ", startCase(contract?.resourceType.toLowerCase()), ' ', _jsx("span", { className: "marketplace-lakehouse-text__emphasis", children: contract?.accessPointGroup }), ' ', "on Data Product", ' ', _jsx("span", { className: "marketplace-lakehouse-text__emphasis", children: contract?.resourceId }), ":"] }), _jsx("div", { children: _jsx("code", { children: errorMessage }) })] }, task.taskId));
})] }))] }), _jsxs(DialogActions, { children: [_jsxs(Button, { onClick: () => {
// eslint-disable-next-line no-void
void handleAction();
}, variant: "contained", disabled: isLoading || errorMessages.length > 0, color: action === 'approve' ? 'success' : 'error', children: [action === 'approve' ? 'Approve' : 'Deny', " Selected Contracts"] }), _jsx(Button, { onClick: handleClose, variant: "outlined", disabled: isLoading, children: "Cancel" })] })] }));
};
export const EntitlementsPendingTasksDashboard = observer((props) => {
// State and props
const { dashboardState } = props;
const pendingTasks = dashboardState.pendingTasks;
const pendingTaskContracts = dashboardState.pendingTaskContracts;
const privilegeManagerTasks = useMemo(() => pendingTasks?.filter((task) => task.type === V1_ApprovalType.CONSUMER_PRIVILEGE_MANAGER_APPROVAL) ?? [], [pendingTasks]);
const dataOwnerTasks = useMemo(() => pendingTasks?.filter((task) => task.type === V1_ApprovalType.DATA_OWNER_APPROVAL) ?? [], [pendingTasks]);
const otherTasks = useMemo(() => pendingTasks?.filter((task) => !privilegeManagerTasks.includes(task) &&
!dataOwnerTasks.includes(task)) ?? [], [dataOwnerTasks, privilegeManagerTasks, pendingTasks]);
const loading = dashboardState.fetchingPendingTasksState.isInProgress;
const marketplaceBaseStore = useLegendMarketplaceBaseStore();
const [selectedAction, setSelectedAction] = useState();
const selectedTaskIdsSet = dashboardState.selectedTaskIds;
const [selectedContract, setSelectedContract] = useState();
const [selectedContractTargetUser, setSelectedContractTargetUser] = useState();
const [contractErrors, setContractErrors] = useState(undefined);
const auth = useAuth();
// Callbacks
const handleFirstDataRendered = (event) => {
const nodesToSelect = [];
event.api.forEachNode((node) => {
if (node.data && selectedTaskIdsSet.has(node.data.taskId)) {
nodesToSelect.push(node);
}
});
event.api.setNodesSelected({ nodes: nodesToSelect, newValue: true });
};
const handleCellClicked = async (event) => {
if (event.colDef.colId !== 'selection') {
const contract = pendingTaskContracts.find((c) => c.guid === event.data?.dataContractId);
setSelectedContract(contract);
setSelectedContractTargetUser(event.data?.consumer);
setContractErrors(undefined);
if (contract !== undefined) {
const result = await dashboardState.getContractErrors(contract.guid, auth.user?.access_token);
setContractErrors(result);
}
}
};
const rowSelection = useMemo(() => ({
mode: 'multiRow',
checkboxes: false,
headerCheckbox: false,
}), []);
const CustomSelectionRenderer = useCallback((params) => {
const handleChange = (_) => {
if (params.data) {
const newSet = new Set(selectedTaskIdsSet);
if (selectedTaskIdsSet.has(params.data.taskId)) {
newSet.delete(params.data.taskId);
}
else {
newSet.add(params.data.taskId);
}
dashboardState.setSelectedTaskIds(newSet);
}
};
return (_jsx(Checkbox, { size: "large", checked: selectedTaskIdsSet.has(params.data?.taskId ?? ''), onChange: handleChange, sx: { padding: 0 } }));
}, [selectedTaskIdsSet, dashboardState]);
const CustomSelectionHeaderRenderer = useCallback((_props) => {
const { taskSet } = _props;
const checked = taskSet.length > 0 &&
taskSet.every((task) => selectedTaskIdsSet.has(task.taskId));
const indeterminate = taskSet.length > 0 &&
!checked &&
taskSet.some((task) => selectedTaskIdsSet.has(task.taskId));
const handleChange = (_e) => {
if (!checked || indeterminate) {
const newSet = new Set(selectedTaskIdsSet);
taskSet.forEach((task) => newSet.add(task.taskId));
dashboardState.setSelectedTaskIds(newSet);
}
else {
const newSet = new Set(selectedTaskIdsSet);
taskSet.forEach((task) => newSet.delete(task.taskId));
dashboardState.setSelectedTaskIds(newSet);
}
};
return (_jsx(Checkbox, { size: "large", checked: checked, indeterminate: indeterminate, onChange: handleChange, disabled: taskSet.length === 0, sx: { padding: 0 } }));
}, [selectedTaskIdsSet, dashboardState]);
const colDefs = useMemo(() => [
{
headerName: 'Date Created',
colId: 'dateCreated',
valueGetter: (params) => {
const contractId = params.data?.dataContractId;
const createdAt = pendingTaskContracts.find((c) => c.guid === contractId)?.createdAt;
return formatOrderDate(createdAt) ?? 'Unknown';
},
sortable: true,
sort: 'desc',
comparator: (_, __, val1, val2) => {
const contractId1 = val1.data?.dataContractId;
const contractId2 = val2.data?.dataContractId;
const createdAt1 = pendingTaskContracts.find((c) => c.guid === contractId1)?.createdAt;
const createdAt2 = pendingTaskContracts.find((c) => c.guid === contractId2)?.createdAt;
const dateA = createdAt1 ? new Date(createdAt1).getTime() : 0;
const dateB = createdAt2 ? new Date(createdAt2).getTime() : 0;
return dateA - dateB;
},
},
{
minWidth: 25,
sortable: true,
resizable: true,
colId: 'consumerType',
headerName: 'Consumer Type',
flex: 1,
valueGetter: (params) => {
const contractId = params.data?.dataContractId;
const consumer = pendingTaskContracts.find((c) => c.guid === contractId)?.consumer;
const typeName = consumer
? getOrganizationalScopeTypeName(consumer, dashboardState.lakehouseEntitlementsStore.applicationStore.pluginManager.getApplicationPlugins())
: undefined;
return typeName ?? 'Unknown';
},
cellRenderer: (params) => {
const contractId = params.data?.dataContractId;
const consumer = pendingTaskContracts.find((c) => c.guid === contractId)?.consumer;
const typeName = consumer
? getOrganizationalScopeTypeName(consumer, dashboardState.lakehouseEntitlementsStore.applicationStore.pluginManager.getApplicationPlugins())
: undefined;
const typeDetails = consumer
? getOrganizationalScopeTypeDetails(consumer, dashboardState.lakehouseEntitlementsStore.applicationStore.pluginManager.getApplicationPlugins())
: undefined;
return (_jsxs(_Fragment, { children: [typeName ?? 'Unknown', typeDetails !== undefined && (_jsx(Tooltip, { className: "marketplace-lakehouse-entitlements__grid__consumer-type__tooltip__icon", title: typeDetails, children: _jsx(InfoCircleIcon, {}) }))] }));
},
},
{
minWidth: 50,
sortable: true,
resizable: true,
colId: 'targetUser',
headerName: 'Target User',
flex: 1,
valueGetter: (params) => {
return params.data?.consumer ?? 'Unknown';
},
cellRenderer: (params) => {
return (_jsx(UserRenderer, { userId: params.data?.consumer, applicationStore: marketplaceBaseStore.applicationStore, userSearchService: marketplaceBaseStore.userSearchService, disableOnClick: true, className: "marketplace-lakehouse-entitlements__grid__user-display" }));
},
},
{
minWidth: 50,
sortable: true,
resizable: true,
colId: 'requester',
headerName: 'Requester',
flex: 1,
valueGetter: (params) => {
const contractId = params.data?.dataContractId;
const requester = pendingTaskContracts.find((c) => c.guid === contractId)?.createdBy;
return requester ?? 'Unknown';
},
cellRenderer: (params) => {
const contractId = params.data?.dataContractId;
const requester = pendingTaskContracts.find((c) => c.guid === contractId)?.createdBy;
return requester ? (_jsx(UserRenderer, { userId: requester, applicationStore: marketplaceBaseStore.applicationStore, userSearchService: marketplaceBaseStore.userSearchService, disableOnClick: true, className: "marketplace-lakehouse-entitlements__grid__user-display" })) : (_jsx(_Fragment, { children: "Unknown" }));
},
},
{
minWidth: 50,
sortable: true,
resizable: true,
headerName: 'Target Data Product',
flex: 1,
valueGetter: (params) => {
const contractId = params.data?.dataContractId;
const contract = pendingTaskContracts.find((c) => c.guid === contractId);
return contract?.resourceId ?? 'Unknown';
},
},
{
minWidth: 50,
sortable: true,
resizable: true,
headerName: 'Target Access Point Group',
flex: 1,
valueGetter: (params) => {
const contractId = params.data?.dataContractId;
const contract = pendingTaskContracts.find((c) => c.guid === contractId);
const accessPointGroup = contract?.resourceType === V1_ResourceType.ACCESS_POINT_GROUP
? contract.accessPointGroup
: `${contract?.accessPointGroup ?? 'Unknown'} (${contract?.resourceType ?? 'Unknown Type'})`;
return accessPointGroup ?? 'Unknown';
},
},
{
minWidth: 50,
sortable: true,
resizable: true,
headerName: 'Business Justification',
flex: 2,
valueGetter: (params) => {
const contractId = params.data?.dataContractId;
const businessJustification = pendingTaskContracts.find((c) => c.guid === contractId)?.description;
return businessJustification ?? 'Unknown';
},
},
{
minWidth: 50,
sortable: true,
resizable: true,
hide: true,
headerName: 'Contract ID',
flex: 2,
valueGetter: (params) => params.data?.dataContractId ?? 'Unknown',
},
], [
dashboardState.lakehouseEntitlementsStore.applicationStore
.pluginManager,
marketplaceBaseStore.applicationStore,
marketplaceBaseStore.userSearchService,
pendingTaskContracts,
]);
const privilegeManagerColDefs = useMemo(() => [
{
headerName: '',
colId: 'selection',
width: 50,
cellRenderer: CustomSelectionRenderer,
headerComponent: (params) => (_jsx(CustomSelectionHeaderRenderer, { params: params, taskSet: privilegeManagerTasks })),
pinned: 'left',
},
...colDefs,
], [
CustomSelectionHeaderRenderer,
CustomSelectionRenderer,
colDefs,
privilegeManagerTasks,
]);
const dataOwnerColDefs = useMemo(() => [
{
headerName: '',
colId: 'selection',
width: 50,
cellRenderer: CustomSelectionRenderer,
headerComponent: (params) => (_jsx(CustomSelectionHeaderRenderer, { params: params, taskSet: dataOwnerTasks })),
pinned: 'left',
},
...colDefs,
], [
CustomSelectionHeaderRenderer,
CustomSelectionRenderer,
colDefs,
dataOwnerTasks,
]);
const otherTasksColDefs = useMemo(() => [
{
headerName: '',
colId: 'selection',
width: 50,
cellRenderer: CustomSelectionRenderer,
headerComponent: (params) => (_jsx(CustomSelectionHeaderRenderer, { params: params, taskSet: otherTasks })),
pinned: 'left',
},
...colDefs,
], [
CustomSelectionHeaderRenderer,
CustomSelectionRenderer,
colDefs,
otherTasks,
]);
return (_jsxs(_Fragment, { children: [_jsxs(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks", children: [_jsxs(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__action-btns", children: [_jsxs(Button, { variant: "contained", color: "success", disabled: !selectedTaskIdsSet.size || loading, onClick: () => setSelectedAction('approve'), children: ["Approve ", selectedTaskIdsSet.size, " tasks"] }), _jsxs(Button, { variant: "contained", color: "error", disabled: !selectedTaskIdsSet.size || loading, onClick: () => setSelectedAction('deny'), children: ["Deny ", selectedTaskIdsSet.size, " tasks"] })] }), _jsxs(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grids", children: [_jsxs(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grid-container", children: [_jsxs(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grid__header", children: ["Privilege Manager Approvals", _jsx(Tooltip, { className: "marketplace-lakehouse-entitlements__pending-tasks__tooltip__icon", title: "These are pending requests for which you are listed as a Privilege Manager.", slotProps: {
tooltip: {
className: 'marketplace-lakehouse-entitlements__pending-tasks__tooltip',
},
}, children: _jsx(InfoCircleIcon, {}) })] }), _jsx(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grid ag-theme-balham", children: _jsx(DataGrid, { rowData: privilegeManagerTasks, onRowDataUpdated: (params) => {
params.api.refreshCells({ force: true });
}, suppressFieldDotNotation: true, suppressContextMenu: false, rowHeight: 45, rowSelection: rowSelection, onFirstDataRendered: handleFirstDataRendered, onCellClicked: (event) =>
// eslint-disable-next-line no-void
void handleCellClicked(event), columnDefs: privilegeManagerColDefs, overlayNoRowsTemplate: "You have no contracts to approve as a Privilege Manager", loading: loading, overlayLoadingTemplate: "Loading contracts" }) })] }), _jsxs(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grid-container", children: [_jsxs(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grid__header", children: ["Data Owner Approvals", _jsx(Tooltip, { className: "marketplace-lakehouse-entitlements__pending-tasks__tooltip__icon", title: "These are pending requests for which you are listed as a Data Owner.", slotProps: {
tooltip: {
className: 'marketplace-lakehouse-entitlements__pending-tasks__tooltip',
},
}, children: _jsx(InfoCircleIcon, {}) })] }), _jsx(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grid ag-theme-balham", children: _jsx(DataGrid, { rowData: dataOwnerTasks, onRowDataUpdated: (params) => {
params.api.refreshCells({ force: true });
}, suppressFieldDotNotation: true, suppressContextMenu: false, rowHeight: 45, rowSelection: rowSelection, onFirstDataRendered: handleFirstDataRendered, onCellClicked: (event) =>
// eslint-disable-next-line no-void
void handleCellClicked(event), columnDefs: dataOwnerColDefs, overlayNoRowsTemplate: "You have no contracts to approve as a Data Owner", loading: loading, overlayLoadingTemplate: "Loading contracts" }) })] }), otherTasks.length > 0 && (_jsxs(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grid-container", children: [_jsx(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grid__header", children: "Other Approvals" }), _jsx(Box, { className: "marketplace-lakehouse-entitlements__pending-tasks__grid ag-theme-balham", children: _jsx(DataGrid, { rowData: otherTasks, onRowDataUpdated: (params) => {
params.api.refreshCells({ force: true });
}, suppressFieldDotNotation: true, suppressContextMenu: false, rowHeight: 45, rowSelection: rowSelection, onFirstDataRendered: handleFirstDataRendered, onCellClicked: (event) =>
// eslint-disable-next-line no-void
void handleCellClicked(event), columnDefs: otherTasksColDefs, loading: loading, overlayLoadingTemplate: "Loading contracts" }) })] }))] })] }), _jsx(EntitlementsDashboardActionModal, { open: selectedAction !== undefined, selectedTasks: pendingTasks?.filter((task) => selectedTaskIdsSet.has(task.taskId)) ?? [], dashboardState: dashboardState, onClose: () => setSelectedAction(undefined), action: selectedAction, pendingTaskContracts: pendingTaskContracts, marketplaceBaseStore: marketplaceBaseStore }), selectedContract !== undefined && (_jsx(DataAccessRequestViewer, { open: true, onClose: () => {
setSelectedContract(undefined);
setContractErrors(undefined);
}, contractErrors: contractErrors, viewerState: new DataContractViewerState(selectedContract, (contractId, taskId) => marketplaceBaseStore.applicationStore.navigationService.navigator.generateAddress(generateContractPagePath(contractId, taskId)), undefined, marketplaceBaseStore.applicationStore, marketplaceBaseStore.lakehouseContractServerClient, new GraphManagerState(marketplaceBaseStore.applicationStore.pluginManager, marketplaceBaseStore.applicationStore.logService), marketplaceBaseStore.userSearchService), onRefresh: async () => {
await flowResult(dashboardState.updateContract(selectedContract.guid, auth.user?.access_token));
}, getDataProductUrl: (dataProductId, deploymentId) => marketplaceBaseStore.applicationStore.navigationService.navigator.generateAddress(generateLakehouseDataProductPath(dataProductId, deploymentId)), initialSelectedUser: selectedContractTargetUser,
//Derives environment from the fact that other environments are filtered out
dataProductEnvironment: marketplaceBaseStore.envState.lakehouseEnvironment }))] }));
});
//# sourceMappingURL=EntitlementsPendingTasksDashboard.js.map