UNPKG

@adaptabletools/adaptable

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

266 lines (265 loc) 13.6 kB
import { createUuid } from '../../AdaptableState/Uuid'; import ObjectFactory from '../ObjectFactory'; export class TeamSharingService { constructor(adaptableApi) { this.adaptableApi = adaptableApi; this.dismissedNotifications = []; const teamSharingOptions = adaptableApi.optionsApi.getTeamSharingOptions(); if (teamSharingOptions.updateInterval > 0) { // convert minutes to millis const updateInterval = teamSharingOptions.updateInterval * 60000; this.updateCheckTimerId = setInterval(() => { this.adaptableApi.teamSharingApi.checkForUpdates(); }, updateInterval); } } buildCustomSharedEntity(Entity, Configuration) { const currentTime = Date.now(); const currentUser = this.adaptableApi.optionsApi.getUserName(); return { EntityType: 'customEntity', Entity, Uuid: Configuration.Uuid ?? createUuid(), Name: Configuration.Name, Description: Configuration.Description, Tags: Configuration.Tags, UserName: currentUser, Timestamp: currentTime, ChangedBy: currentUser, ChangedAt: currentTime, }; } buildSharedEntityWithDependencies(adaptableObject, module, configuration) { const sharingUserName = this.adaptableApi.optionsApi.getUserName(); const sharingTimestamp = new Date().getTime(); const createdSharedEntities = []; this.createSharedEntity(adaptableObject, module, configuration, sharingUserName, sharingTimestamp, createdSharedEntities); return createdSharedEntities; } getSharedEntityDependants(sharedEntityId, sharedEntities) { return sharedEntities.filter((sharedEntity) => sharedEntity.EntityDependencyIds.includes(sharedEntityId)); } updateActiveSharedEntity(changedAdaptableObject, userName, sharedEntities) { let activeSharedEntity = sharedEntities.find((sharedEntity) => sharedEntity.Type === 'Active' && sharedEntity.Entity.Uuid === changedAdaptableObject.Uuid); if (!activeSharedEntity) { // shared entity may have been removed in the meantime return [sharedEntities, []]; } activeSharedEntity = { ...activeSharedEntity, Entity: changedAdaptableObject, ChangedAt: Date.now(), ChangedBy: userName, Revision: activeSharedEntity.Revision + 1, }; // update EntityDependencyIds (references may have been removed or new ones added) const updatedEntityDependencyIds = []; const currentEntityDependencies = this.getSharedEntityDependencies(activeSharedEntity, sharedEntities); const newTeamSharingReferences = this.getTeamSharingReferences(changedAdaptableObject, activeSharedEntity.Module); const newReferenceAdaptableObjectIds = newTeamSharingReferences.map((reference) => reference.Reference.Uuid); // 1. add all the existing dependencies which are still referenced updatedEntityDependencyIds.push(...currentEntityDependencies .filter((entityDependency) => newReferenceAdaptableObjectIds.includes(entityDependency.Entity.Uuid)) .map((entity) => entity.Uuid)); // 2. check if there are new references which are not yet shared const freshReferences = newTeamSharingReferences.filter((reference) => !currentEntityDependencies .map((entityDependency) => entityDependency.Entity.Uuid) .includes(reference.Reference.Uuid)); const newSharedEntityDependencies = []; freshReferences.forEach((reference) => { this.createSharedEntity(reference.Reference, reference.Module, { description: activeSharedEntity.Description, type: activeSharedEntity.Type, }, activeSharedEntity.UserName, Date.now(), newSharedEntityDependencies); }); updatedEntityDependencyIds.push(...newSharedEntityDependencies.map((sharedEntity) => sharedEntity.Uuid)); activeSharedEntity.EntityDependencyIds = updatedEntityDependencyIds; const updatedSharedEntities = sharedEntities.map((sharedEntity) => sharedEntity.Uuid === activeSharedEntity.Uuid ? activeSharedEntity : sharedEntity); updatedSharedEntities.push(...newSharedEntityDependencies); const updatedActiveEntities = [activeSharedEntity, ...newSharedEntityDependencies]; return [updatedSharedEntities, updatedActiveEntities]; } buildSharedEntityImportActions(importedSharedEntity) { let configOverwriteNeedsConfirmation = false; // either ADD or EDIT(in which case the user will need to confirm the overwrite) const getSharedEntityImportAction = (sharedEntity) => { const { Module, Entity } = sharedEntity; const importInfo = this.adaptableApi.internalApi .getModuleService() .getTeamSharingAction(Module); if (importInfo?.ModuleEntities.some((moduleEntity) => moduleEntity.Uuid === Entity.Uuid)) { configOverwriteNeedsConfirmation = true; return importInfo?.EditAction(Entity); } else { return importInfo?.AddAction(Entity); } }; const existingSharedEntities = this.adaptableApi.teamSharingApi.getLoadedAdaptableSharedEntities(); const sharedEntityDependencyTree = this.getSharedEntityDependencyTree(importedSharedEntity, existingSharedEntities); const importSteps = sharedEntityDependencyTree.map((sharedEntity) => ({ sharedEntity, importAction: getSharedEntityImportAction(sharedEntity), })); return [importSteps, configOverwriteNeedsConfirmation]; } getSharedEntityLocalAndRemoteRevisions(changedAdaptableObjectId, remoteSharedEntities) { const localRevision = this.adaptableApi.internalApi.getState().TeamSharing.ActiveSharedEntityMap[changedAdaptableObjectId]?.Revision ?? -1; const remoteRevision = remoteSharedEntities.find((sharedEntity) => sharedEntity.Entity.Uuid === changedAdaptableObjectId)?.Revision ?? -1; return [localRevision, remoteRevision]; } getStaleActiveSharedEntities() { const sharedEntities = this.adaptableApi.teamSharingApi.getLoadedAdaptableSharedEntities(); const activeEntities = this.adaptableApi.internalApi.getState().TeamSharing.ActiveSharedEntityMap; const result = {}; sharedEntities .filter((sharedEntity) => sharedEntity.Type === 'Active') .forEach((sharedEntity) => { const importedRevision = activeEntities[sharedEntity.Entity.Uuid]?.Revision ?? Number.MAX_VALUE; if (sharedEntity.Revision > importedRevision) { result[sharedEntity.Uuid] = { sharedEntity, sharedRevision: sharedEntity.Revision, importedRevision: activeEntities[sharedEntity.Entity.Uuid].Revision, }; } }); return result; } showUpdateNotifications() { const { updateNotification } = this.adaptableApi.optionsApi.getTeamSharingOptions(); if (!updateNotification) { return; } Object.values(this.getStaleActiveSharedEntities()).forEach((sharedEntityActiveInfo) => { const notificationMessage = `Active share ${sharedEntityActiveInfo.sharedEntity.Module} has a new Revision: ${sharedEntityActiveInfo.sharedEntity.Revision}`; if (updateNotification === 'Alert' || updateNotification === 'AlertWithNotification') { this.showUpdateNotificationAlert(sharedEntityActiveInfo, notificationMessage); } if (updateNotification === 'SystemStatus') { this.adaptableApi.systemStatusApi.setInfoSystemStatus(`TeamSharing: ${notificationMessage}`); } this.adaptableApi.eventApi.internalApi.fireTeamSharingEntityChangedEvent(sharedEntityActiveInfo.sharedEntity); }); } showUpdateNotificationAlert(sharedEntityActiveInfo, message) { const { updateNotification, showUpdateNotificationOncePerUpdate } = this.adaptableApi.optionsApi.getTeamSharingOptions(); if (this.dismissedNotifications.some((notification) => notification.entityUuid === sharedEntityActiveInfo.sharedEntity.Uuid)) { return; } const buttons = [ { Label: 'Ok', ButtonStyle: { tone: 'neutral', variant: 'raised', }, }, { Label: 'Import', Command: (context) => { this.adaptableApi.teamSharingApi.importSharedEntry(sharedEntityActiveInfo.sharedEntity); }, ButtonStyle: { tone: 'info', variant: 'raised', }, }, ]; if (!showUpdateNotificationOncePerUpdate) { buttons.push({ Label: 'Dismiss', ButtonStyle: { tone: 'error', variant: 'raised', }, Command: () => { this.dismissedNotifications.push({ entityUuid: sharedEntityActiveInfo.sharedEntity.Uuid, }); }, }); } const alert = { alertType: 'generic', header: 'Team Sharing Update', message: message, alertDefinition: { ...ObjectFactory.CreateEmptyAlertDefinition(), MessageType: 'Info', AlertProperties: { DisplayNotification: updateNotification === 'AlertWithNotification', NotificationDuration: 'always', }, AlertForm: { Buttons: buttons, }, }, }; this.adaptableApi.alertApi.displayAdaptableAlert(alert); if (showUpdateNotificationOncePerUpdate) { this.dismissedNotifications.push({ entityUuid: sharedEntityActiveInfo.sharedEntity.Uuid, }); } } destroy() { clearTimeout(this.updateCheckTimerId); } createSharedEntity(adaptableObject, module, configuration, sharingUserName, sharingTimestamp, createdSharedEntities) { // create main shared entity const mainSharedEntity = { EntityType: 'adaptableEntity', Uuid: createUuid(), Entity: adaptableObject, EntityDependencyIds: [], Module: module, Description: configuration.description, UserName: sharingUserName, Timestamp: sharingTimestamp, ChangedBy: sharingUserName, ChangedAt: sharingTimestamp, Revision: 1, Type: configuration.type, }; // load all dependencies (special cols, shared queries etc) const teamSharingDependencies = this.getTeamSharingReferences(adaptableObject, module); // for every dependency, create recursively the corresponding shared entities teamSharingDependencies.forEach((teamSharingDependency) => { const sharedEntityDependency = this.createSharedEntity(teamSharingDependency.Reference, teamSharingDependency.Module, configuration, sharingUserName, sharingTimestamp, createdSharedEntities); // update dependency IDs for the main shared entity mainSharedEntity.EntityDependencyIds.push(sharedEntityDependency.Uuid); }); // add main shared entity to the accumulator createdSharedEntities.push(mainSharedEntity); return mainSharedEntity; } // returns the given rootSharedEntity with all its unique dependencies in their dependency order getSharedEntityDependencyTree(rootSharedEntity, allSharedEntities) { // using a map to handle the case where a SharedEntity occurs multiple times in the dependency tree const result = new Map(); result.set(rootSharedEntity.Uuid, rootSharedEntity); rootSharedEntity.EntityDependencyIds.forEach((dependencyId) => { const dependencySharedEntity = allSharedEntities.find((sharedEntity) => sharedEntity.Uuid === dependencyId); // check for zombie dependencies (possible if state is inconsistent due to dirty reads) if (dependencySharedEntity) { this.getSharedEntityDependencyTree(dependencySharedEntity, allSharedEntities).forEach((childDependency) => { // check if this dependency is not already present in the dependency tree if (!result.has(childDependency.Uuid)) { result.set(childDependency.Uuid, childDependency); } }); } }); return Array.from(result.values()); } getTeamSharingReferences(adaptableObject, module) { return (this.adaptableApi.internalApi .getModuleService() .getModuleById(module) ?.getTeamSharingReferences(adaptableObject) ?? []); } getSharedEntityDependencies(input, allSharedEntities) { return input.EntityDependencyIds.map((dependencyId) => allSharedEntities.find((sharedEntity) => sharedEntity.Uuid === dependencyId)).filter((dependencyEntity) => !!dependencyEntity); } }