@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
JavaScript
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);
}
}