Socket
Socket
Sign inDemoInstall

@jupiterone/integration-sdk-cli

Package Overview
Dependencies
Maintainers
12
Versions
268
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jupiterone/integration-sdk-cli - npm Package Compare versions

Comparing version 2.8.0 to 2.9.0

dist/src/visualization/utils.d.ts

12

dist/src/visualization/createMappedRelationshipNodesAndEdges.d.ts
import { Node, Edge } from "vis";
import { Entity, MappedRelationship, RelationshipDirection } from "@jupiterone/integration-sdk-core";
import { Entity, MappedRelationship, RelationshipDirection, RelationshipMapping } from "@jupiterone/integration-sdk-core";
import { NodeEntity } from "./utils";
interface MappedRelationshipNodesAndEdges {

@@ -7,4 +8,9 @@ mappedRelationshipNodes: Node[];

}
export declare function createMappedRelationshipNodesAndEdges(mappedRelationships: MappedRelationship[], explicitEntities: Entity[]): MappedRelationshipNodesAndEdges;
export declare function createMappedRelationshipEdge(sourceKey: string, targetKey: string, label: string | undefined, relationshipDirection: RelationshipDirection): Edge;
export declare function isClassMatch(entityClasses: string | string[] | undefined, targetEntityClasses: string | string[] | undefined): boolean;
export declare function findTargetEntity(entities: NodeEntity[], _mapping: RelationshipMapping): NodeEntity | undefined;
export declare function createMappedRelationshipNodesAndEdges(options: {
mappedRelationships: MappedRelationship[];
explicitEntities: Entity[];
}): MappedRelationshipNodesAndEdges;
export declare function createMappedRelationshipEdge(sourceNodeId: string, targetNodeId: string, label: string | undefined, relationshipDirection: RelationshipDirection): Edge;
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMappedRelationshipEdge = exports.createMappedRelationshipNodesAndEdges = void 0;
exports.createMappedRelationshipEdge = exports.createMappedRelationshipNodesAndEdges = exports.findTargetEntity = exports.isClassMatch = void 0;
const integration_sdk_core_1 = require("@jupiterone/integration-sdk-core");
const lodash_1 = require("lodash");
const uuid_1 = require("uuid");
function createMappedRelationshipNodesAndEdges(mappedRelationships, explicitEntities) {
var _a, _b;
const utils_1 = require("./utils");
function isClassMatch(entityClasses, targetEntityClasses) {
if (targetEntityClasses === undefined) {
return true;
}
else if (!Array.isArray(targetEntityClasses)) {
targetEntityClasses = [targetEntityClasses];
}
if (entityClasses === undefined) {
return false;
}
else if (!Array.isArray(entityClasses)) {
entityClasses = [entityClasses];
}
return targetEntityClasses.every((targetClass) => entityClasses.includes(targetClass));
}
exports.isClassMatch = isClassMatch;
function findTargetEntity(entities, _mapping) {
for (const targetFilterKey of _mapping.targetFilterKeys) {
const targetEntity = lodash_1.pick(_mapping.targetEntity, targetFilterKey);
const matchedEntity = entities.find((entity) => {
const { _class: entityClass, ...entityRest } = entity;
const { _class: targetEntityClass, ...targetEntityRest } = targetEntity;
return lodash_1.isMatch(entityRest, targetEntityRest) && isClassMatch(entityClass, targetEntityClass);
});
if (matchedEntity !== undefined)
return matchedEntity;
}
}
exports.findTargetEntity = findTargetEntity;
function isNewMissingEntity(sourceEntity) {
return sourceEntity.nodeId === undefined;
}
function findOrCreateSourceEntity(explicitEntities, sourceEntityKey) {
const sourceEntity = explicitEntities.find((e) => e._key === sourceEntityKey);
if (sourceEntity === undefined) {
// This should never happen! The "sourceEntity" ought to exist in the integration.
return {
_key: sourceEntityKey,
};
}
else {
return sourceEntity;
}
}
function isNewPlaceholderEntity(targetEntity) {
return targetEntity.nodeId === undefined;
}
;
function findOrCreatePlaceholderEntity(nodeEntities, _mapping) {
const targetEntity = findTargetEntity(nodeEntities, _mapping);
if (targetEntity === undefined) {
return _mapping.targetEntity;
}
else {
return targetEntity;
}
}
function createMappedRelationshipNodesAndEdges(options) {
const { mappedRelationships, explicitEntities } = options;
const mappedRelationshipNodes = [];
const mappedRelationshipEdges = [];
const entities = [...explicitEntities];
const explicitNodeEntities = [...explicitEntities.map((e) => ({ ...e, nodeId: utils_1.getNodeIdFromEntity(e, []) }))];
const missingNodeEntities = [];
const placeholderNodeEntities = [];
for (const mappedRelationship of mappedRelationships) {
let sourceKey = (_a = explicitEntities.find((e) => e._key === mappedRelationship._mapping.sourceEntityKey)) === null || _a === void 0 ? void 0 : _a._key;
if (sourceKey === undefined) {
// This should never happen! The "sourceEntity" ought to exist in the integration.
const sourceNode = createMissingEntity(mappedRelationship._mapping.sourceEntityKey);
const sourceEntity = findOrCreateSourceEntity([
...explicitNodeEntities,
...missingNodeEntities
], mappedRelationship._mapping.sourceEntityKey);
let sourceNodeId;
if (isNewMissingEntity(sourceEntity)) {
const missingSourceEntity = {
...sourceEntity,
nodeId: utils_1.getNodeIdFromEntity(sourceEntity, [
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
]),
};
missingNodeEntities.push(missingSourceEntity);
const sourceNode = createMissingEntityNode(missingSourceEntity);
mappedRelationshipNodes.push(sourceNode);
sourceKey = sourceNode.id;
sourceNodeId = missingSourceEntity.nodeId;
}
let targetKey = (_b = entities.find((e) => lodash_1.isMatch(e, mappedRelationship._mapping.targetEntity))) === null || _b === void 0 ? void 0 : _b._key;
if (targetKey === undefined) {
const targetNode = createPlaceholderEntity(mappedRelationship._mapping.targetEntity);
entities.push({
_key: targetNode.id,
...(mappedRelationship._mapping.targetEntity),
});
else {
sourceNodeId = utils_1.getNodeIdFromEntity(sourceEntity, [
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
]);
}
const targetEntity = findOrCreatePlaceholderEntity([
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
], mappedRelationship._mapping);
let targetNodeId;
if (isNewPlaceholderEntity(targetEntity)) {
const placeholderTargetEntity = {
...targetEntity,
nodeId: utils_1.getNodeIdFromEntity(targetEntity, [
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
]),
};
placeholderNodeEntities.push(placeholderTargetEntity);
const targetNode = createPlaceholderEntityNode(placeholderTargetEntity);
mappedRelationshipNodes.push(targetNode);
targetKey = targetNode.id;
targetNodeId = placeholderTargetEntity.nodeId;
}
mappedRelationshipEdges.push(createMappedRelationshipEdge(sourceKey, targetKey, mappedRelationship.displayName, mappedRelationship._mapping.relationshipDirection));
else {
targetNodeId = utils_1.getNodeIdFromEntity(targetEntity, [
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
]);
}
mappedRelationshipEdges.push(createMappedRelationshipEdge(sourceNodeId, targetNodeId, mappedRelationship.displayName, mappedRelationship._mapping.relationshipDirection));
}

@@ -38,14 +133,29 @@ return {

exports.createMappedRelationshipNodesAndEdges = createMappedRelationshipNodesAndEdges;
function createEntityNode(node, options) {
const overrides = {};
const labelBody = options.labelBody || node.label;
if (options.isDuplicateId === true) {
options.labelHeader = '[DUPLICATE _KEY]' + options.labelHeader || '';
overrides.color = 'red';
}
if (options.labelHeader !== undefined) {
overrides.label = `<b>${options.labelHeader}</b>\n${labelBody}`;
overrides.font = { multi: 'html' };
}
return {
...node,
...overrides,
};
}
const MISSING_GROUP = 'missing';
function createMissingEntity(sourceEntityKey) {
return {
id: sourceEntityKey,
label: `<b>[MISSING ENTITY]</b>\n${sourceEntityKey}`,
function createMissingEntityNode(sourceEntity) {
return createEntityNode({
id: sourceEntity.nodeId,
color: 'red',
group: MISSING_GROUP,
font: {
// required: enables displaying <b>text</b> in the label as bold text
multi: 'html',
}
};
}, {
isDuplicateId: utils_1.isNodeIdDuplicate(sourceEntity),
labelHeader: '[MISSING ENTITY]',
labelBody: sourceEntity._key,
});
}

@@ -57,21 +167,26 @@ const UNKNOWN_GROUP = 'unknown';

*/
function getWrappedJsonString(JSONableObject) {
function getWrappedJsonString(options) {
const { JSONableObject, keysToOmit } = options;
const keyValueArray = [];
for (const key of Object.keys(JSONableObject)) {
keyValueArray.push(`${key}: ${JSON.stringify(JSONableObject[key])}`);
if (!keysToOmit.includes(key)) {
keyValueArray.push(`${key}: ${JSON.stringify(JSONableObject[key])}`);
}
}
return keyValueArray.join('\n');
}
function createPlaceholderEntity(targetEntity) {
return {
id: targetEntity._key || uuid_1.v4(),
label: `<b>[PLACEHOLDER ENTITY]</b>\n${getWrappedJsonString(targetEntity)}`,
function getWrappedEntityPropertiesString(nodeEntity) {
return getWrappedJsonString({ JSONableObject: nodeEntity, keysToOmit: ['nodeId'] });
}
function createPlaceholderEntityNode(targetEntity) {
return createEntityNode({
id: targetEntity.nodeId,
group: targetEntity._type || UNKNOWN_GROUP,
font: {
// required: enables displaying <b>text</b> in the label as bold text
multi: 'html',
}
};
}, {
isDuplicateId: utils_1.isNodeIdDuplicate(targetEntity),
labelHeader: '[PLACEHOLDER ENTITY]',
labelBody: getWrappedEntityPropertiesString(targetEntity),
});
}
function createMappedRelationshipEdge(sourceKey, targetKey, label, relationshipDirection) {
function createMappedRelationshipEdge(sourceNodeId, targetNodeId, label, relationshipDirection) {
let fromKey;

@@ -81,8 +196,8 @@ let toKey;

case integration_sdk_core_1.RelationshipDirection.FORWARD:
fromKey = sourceKey;
toKey = targetKey;
fromKey = sourceNodeId;
toKey = targetNodeId;
break;
case integration_sdk_core_1.RelationshipDirection.REVERSE:
fromKey = targetKey;
toKey = sourceKey;
fromKey = targetNodeId;
toKey = sourceNodeId;
break;

@@ -89,0 +204,0 @@ }

@@ -34,2 +34,3 @@ "use strict";

const createMappedRelationshipNodesAndEdges_1 = require("./createMappedRelationshipNodesAndEdges");
const utils_1 = require("./utils");
/**

@@ -48,3 +49,3 @@ * Generates visualization of Vertices and Edges using https://visjs.github.io/vis-network/docs/network/

const nodeDataSets = entities.map((entity) => ({
id: entity._key,
id: utils_1.getNodeIdFromEntity(entity, []),
label: `${entity.displayName}\n[${entity._type}]`,

@@ -58,3 +59,6 @@ group: entity._type,

}));
const { mappedRelationshipEdges, mappedRelationshipNodes, } = createMappedRelationshipNodesAndEdges_1.createMappedRelationshipNodesAndEdges(mappedRelationships, entities);
const { mappedRelationshipEdges, mappedRelationshipNodes, } = createMappedRelationshipNodesAndEdges_1.createMappedRelationshipNodesAndEdges({
mappedRelationships,
explicitEntities: entities,
});
const htmlFileLocation = path_1.default.join(resolvedIntegrationPath, 'index.html');

@@ -61,0 +65,0 @@ await integration_sdk_runtime_1.writeFileToPath({

{
"name": "@jupiterone/integration-sdk-cli",
"version": "2.8.0",
"version": "2.9.0",
"description": "The SDK for developing JupiterOne integrations",

@@ -25,3 +25,3 @@ "main": "dist/src/index.js",

"dependencies": {
"@jupiterone/integration-sdk-runtime": "^2.8.0",
"@jupiterone/integration-sdk-runtime": "^2.9.0",
"commander": "^5.0.0",

@@ -34,3 +34,3 @@ "globby": "^11.0.0",

"devDependencies": {
"@jupiterone/integration-sdk-private-test-utils": "^2.8.0",
"@jupiterone/integration-sdk-private-test-utils": "^2.9.0",
"@pollyjs/adapter-node-http": "^4.0.4",

@@ -48,3 +48,3 @@ "@pollyjs/core": "^4.0.4",

},
"gitHead": "813bb415cc027dc84a20dee072ea0cc6e9b78d07"
"gitHead": "468e539a6fc019fb81fc0b451d9c98eef2902be0"
}

@@ -1,329 +0,440 @@

import { createMappedRelationshipNodesAndEdges } from "../createMappedRelationshipNodesAndEdges"
import { createMappedRelationshipNodesAndEdges, isClassMatch, findTargetEntity } from "../createMappedRelationshipNodesAndEdges"
import { RelationshipDirection } from "@jupiterone/integration-sdk-core/src"
const mappedRelationships = [{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: ['_key'],
targetEntity: {
_key: '456',
}
},
_key: 'abc',
_type: 'src_has_target',
_class: 'HAS',
}];
test('should return relationship between two existing entities', () => {
const explicitEntities = [
{
_key: '123',
_type: 'src',
_class: ['Account'],
describe('#createMappedRelationshipNodesAndEdges', () => {
const mappedRelationships = [{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: ['_key'],
targetEntity: {
_key: '456',
}
},
{
_key: '456',
_type: 'target',
_class: ['User'],
}
]
_key: 'abc',
_type: 'src_has_target',
_class: 'HAS',
}];
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges(mappedRelationships, explicitEntities);
test('should return relationship between two existing entities', () => {
const explicitEntities = [
{
_key: '123',
_type: 'src',
_class: ['Account'],
},
{
_key: '456',
_type: 'target',
_class: ['User'],
}
]
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: '456',
label: 'HAS',
dashes: true,
}
]);
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges({mappedRelationships, explicitEntities});
expect(mappedRelationshipNodes).toEqual([]);
});
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: '456',
label: 'HAS',
dashes: true,
}
]);
test('should return MISSING entity when sourceEntityKey is not found in explicitEntities', () => {
const explicitEntities = [
{
_key: '456',
_type: 'target',
_class: ['User'],
}
]
expect(mappedRelationshipNodes).toEqual([]);
});
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges(mappedRelationships, explicitEntities);
test('should return MISSING entity when sourceEntityKey is not found in explicitEntities', () => {
const explicitEntities = [
{
_key: '456',
_type: 'target',
_class: ['User'],
}
]
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: '456',
label: 'HAS',
dashes: true,
}
]);
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges({mappedRelationships, explicitEntities});
expect(mappedRelationshipNodes).toEqual([
{
id: '123',
color: 'red',
font: {
multi: 'html'
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: '456',
label: 'HAS',
dashes: true,
}
]);
expect(mappedRelationshipNodes).toEqual([
{
id: '123',
color: 'red',
font: {
multi: 'html'
},
group: 'missing',
label: '<b>[MISSING ENTITY]</b>\n123',
}
]);
});
test('should return PLACEHOLDER entity when targetEntity is not found in entities', () => {
const explicitEntities = [
{
_key: '123',
_type: 'source',
_class: ['Acccount'],
}
]
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges({mappedRelationships, explicitEntities});
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: '456',
label: 'HAS',
dashes: true,
}
]);
expect(mappedRelationshipNodes).toEqual([
{
id: '456',
font: {
multi: 'html'
},
group: 'unknown',
label: '<b>[PLACEHOLDER ENTITY]</b>\n_key: "456"',
}
]);
});
test('should only return one PLACEHOLDER entity when found in multiple relationships', () => {
const mappedRelationships = [
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: ['_key'],
targetEntity: {
_key: '789',
}
},
_key: 'abc',
_type: 'src_has_target',
_class: 'HAS',
},
group: 'missing',
label: '<b>[MISSING ENTITY]</b>\n123',
}
]);
});
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '456',
targetFilterKeys: ['_key'],
targetEntity: {
_key: '789',
}
},
_key: 'def',
_type: 'src_has_target',
_class: 'HAS',
}
];
const explicitEntities = [
{
_key: '123',
_type: 'source',
_class: ['Acccount'],
},
{
_key: '456',
_type: 'target',
_class: ['User'],
}
]
test('should return PLACEHOLDER entity when targetEntity is not found in entities', () => {
const explicitEntities = [
{
_key: '123',
_type: 'source',
_class: ['Acccount'],
}
]
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges({mappedRelationships, explicitEntities});
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges(mappedRelationships, explicitEntities);
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: '789',
label: 'HAS',
dashes: true,
},
{
from: '456',
to: '789',
label: 'HAS',
dashes: true,
},
]);
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: '456',
label: 'HAS',
dashes: true,
}
]);
expect(mappedRelationshipNodes).toEqual([
{
id: '789',
font: {
multi: 'html'
},
group: 'unknown',
label: '<b>[PLACEHOLDER ENTITY]</b>\n_key: "789"',
}
]);
});
expect(mappedRelationshipNodes).toEqual([
{
id: '456',
font: {
multi: 'html'
test('should return UUID for ID when PLACEHOLDER entity when found in multiple relationships', () => {
const mappedRelationships = [
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: ['_class'],
targetEntity: {
_class: '789',
}
},
_key: 'abc',
_type: 'src_has_target',
_class: 'HAS',
},
group: 'unknown',
label: '<b>[PLACEHOLDER ENTITY]</b>\n_key: "456"',
}
]);
});
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '456',
targetFilterKeys: ['_class'],
targetEntity: {
_class: '789',
}
},
_key: 'def',
_type: 'src_has_target',
_class: 'HAS',
}
];
const explicitEntities = [
{
_key: '123',
_type: 'source',
_class: ['Acccount'],
},
{
_key: '456',
_type: 'target',
_class: ['User'],
}
]
test('should only return one PLACEHOLDER entity when found in multiple relationships', () => {
const mappedRelationships = [
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: ['_key'],
targetEntity: {
_key: '789',
}
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges({mappedRelationships, explicitEntities});
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: expect.any(String),
label: 'HAS',
dashes: true,
},
_key: 'abc',
_type: 'src_has_target',
_class: 'HAS',
},
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '456',
targetFilterKeys: ['_key'],
targetEntity: {
_key: '789',
}
{
from: '456',
to: expect.any(String),
label: 'HAS',
dashes: true,
},
_key: 'def',
_type: 'src_has_target',
_class: 'HAS',
}
];
const explicitEntities = [
{
_key: '123',
_type: 'source',
_class: ['Acccount'],
},
{
_key: '456',
_type: 'target',
_class: ['User'],
}
]
]);
expect(mappedRelationshipEdges[0].to).toEqual(mappedRelationshipEdges[1].to);
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges(mappedRelationships, explicitEntities);
expect(mappedRelationshipNodes).toEqual([
{
id: mappedRelationshipEdges[0].to,
font: {
multi: 'html'
},
group: 'unknown',
label: '<b>[PLACEHOLDER ENTITY]</b>\n_class: "789"',
}
]);
});
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: '789',
label: 'HAS',
dashes: true,
},
{
from: '456',
to: '789',
label: 'HAS',
dashes: true,
},
]);
test('should return node with group === _type when "_type" is targetFilterKey', () => {
const mappedRelationships = [
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: ['_type'],
targetEntity: {
_type: '789',
}
},
_key: 'abc',
_type: 'src_has_target',
_class: 'HAS',
}
];
const explicitEntities = [
{
_key: '123',
_type: 'source',
_class: ['Acccount'],
}
]
expect(mappedRelationshipNodes).toEqual([
{
id: '789',
font: {
multi: 'html'
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges({mappedRelationships, explicitEntities});
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: expect.any(String),
label: 'HAS',
dashes: true,
},
group: 'unknown',
label: '<b>[PLACEHOLDER ENTITY]</b>\n_key: "789"',
}
]);
});
]);
expect(mappedRelationshipNodes).toEqual([
{
id: expect.any(String),
font: {
multi: 'html'
},
group: '789',
label: '<b>[PLACEHOLDER ENTITY]</b>\n_type: "789"',
}
]);
});
test('should return UUID for ID when PLACEHOLDER entity when found in multiple relationships', () => {
const mappedRelationships = [
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: ['_class'],
targetEntity: {
_class: '789',
}
test('should return DUPLICATE KEY when duplicate non-matching entity found', () => {
const mappedRelationships = [
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: [['_key', '_type']],
targetEntity: {
_key: '456',
_type: 'non-matching-target',
}
},
_key: 'abc',
_type: 'src_has_target',
_class: 'HAS',
},
_key: 'abc',
_type: 'src_has_target',
_class: 'HAS',
},
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '456',
targetFilterKeys: ['_class'],
targetEntity: {
_class: '789',
}
];
const explicitEntities = [
{
_key: '123',
_type: 'source',
_class: ['Acccount'],
},
_key: 'def',
_type: 'src_has_target',
_class: 'HAS',
}
];
const explicitEntities = [
{
_key: '123',
_type: 'source',
_class: ['Acccount'],
},
{
_key: '456',
_type: 'target',
_class: ['User'],
}
]
{
_key: '456',
_type: 'target',
_class: ['User'],
}
]
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges(mappedRelationships, explicitEntities);
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges({mappedRelationships, explicitEntities});
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: expect.any(String),
label: 'HAS',
dashes: true,
},
{
from: '456',
to: expect.any(String),
label: 'HAS',
dashes: true,
},
]);
expect(mappedRelationshipEdges[0].to).toEqual(mappedRelationshipEdges[1].to);
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: expect.not.stringMatching('456'),
label: 'HAS',
dashes: true,
},
]);
expect(mappedRelationshipNodes).toEqual([
{
id: mappedRelationshipEdges[0].to,
font: {
multi: 'html'
expect(mappedRelationshipNodes).toEqual([
{
id: mappedRelationshipEdges[0].to,
color: 'red',
font: {
multi: 'html'
},
group: 'non-matching-target',
label: '<b>[DUPLICATE _KEY][PLACEHOLDER ENTITY]</b>\n_key: "456"\n_type: "non-matching-target"',
}
]);
});
});
describe('#findTargetEntity', () => {
test('should not match entity with same key but different other props', () => {
const _mapping = {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: [['_key', '_type']],
targetEntity: {
_key: '456',
_type: 'non-matching-target',
}
};
const nodeEntities = [
{
_key: '456',
_type: 'target',
_class: ['User'],
nodeId: '456',
},
group: 'unknown',
label: '<b>[PLACEHOLDER ENTITY]</b>\n_class: "789"',
}
]);
];
const response = findTargetEntity(nodeEntities, _mapping);
expect(response).toBeUndefined();
})
});
describe('#isClassMatch', () => {
test('should match when matching array', () => {
expect(isClassMatch(['User'], ['User'])).toEqual(true);
});
test('should return node with group === _type when "_type" is targetFilterKey', () => {
const mappedRelationships = [
{
displayName: 'HAS',
_mapping: {
relationshipDirection: RelationshipDirection.FORWARD,
sourceEntityKey: '123',
targetFilterKeys: ['_type'],
targetEntity: {
_type: '789',
}
},
_key: 'abc',
_type: 'src_has_target',
_class: 'HAS',
}
];
const explicitEntities = [
{
_key: '123',
_type: 'source',
_class: ['Acccount'],
}
]
test('should match when matching string', () => {
expect(isClassMatch('User', 'User')).toEqual(true);
});
const {
mappedRelationshipEdges,
mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges(mappedRelationships, explicitEntities);
test('should match when target string is only element in array', () => {
expect(isClassMatch(['User'], 'User')).toEqual(true);
});
expect(mappedRelationshipEdges).toEqual([
{
from: '123',
to: expect.any(String),
label: 'HAS',
dashes: true,
},
]);
test('should match when target string in array', () => {
expect(isClassMatch(['User', 'Group'], 'User')).toEqual(true);
});
expect(mappedRelationshipNodes).toEqual([
{
id: expect.any(String),
font: {
multi: 'html'
},
group: '789',
label: '<b>[PLACEHOLDER ENTITY]</b>\n_type: "789"',
}
]);
test('should match when target array in array', () => {
expect(isClassMatch(['User', 'Group', 'Account'], ['User', 'Group'])).toEqual(true);
});
test('should not match when classes differ', () => {
expect(isClassMatch('User', 'Group')).toEqual(false);
});
});

@@ -1,5 +0,11 @@

import { Node, Edge } from "vis";
import { Entity, MappedRelationship, RelationshipDirection } from "@jupiterone/integration-sdk-core";
import { isMatch } from "lodash";
import { v4 as uuid } from 'uuid';
import { Node, Edge, NodeOptions } from "vis";
import {
Entity,
MappedRelationship,
RelationshipDirection,
RelationshipMapping,
TargetEntityProperties,
} from "@jupiterone/integration-sdk-core";
import { isMatch, pick } from "lodash";
import { getNodeIdFromEntity, NodeEntity, isNodeIdDuplicate } from "./utils";

@@ -11,30 +17,135 @@ interface MappedRelationshipNodesAndEdges {

export function createMappedRelationshipNodesAndEdges(mappedRelationships: MappedRelationship[], explicitEntities: Entity[]): MappedRelationshipNodesAndEdges {
export function isClassMatch(entityClasses: string | string[] | undefined, targetEntityClasses: string | string[] | undefined): boolean {
if (targetEntityClasses === undefined) {
return true;
} else if (!Array.isArray(targetEntityClasses)) {
targetEntityClasses = [targetEntityClasses];
}
if (entityClasses === undefined) {
return false;
} else if (!Array.isArray(entityClasses)) {
entityClasses = [entityClasses];
}
return targetEntityClasses.every((targetClass) => (entityClasses as string[]).includes(targetClass));
}
export function findTargetEntity(entities: NodeEntity[], _mapping: RelationshipMapping): NodeEntity | undefined {
for (const targetFilterKey of _mapping.targetFilterKeys) {
const targetEntity = pick(_mapping.targetEntity, targetFilterKey);
const matchedEntity = entities.find((entity) => {
const { _class: entityClass, ...entityRest } = entity;
const { _class: targetEntityClass, ...targetEntityRest } = targetEntity;
return isMatch(entityRest, targetEntityRest) && isClassMatch(entityClass, (targetEntityClass as string | string[] | undefined));
});
if (matchedEntity !== undefined) return matchedEntity;
}
}
type NewMissingEntity = { _key: string };
function isNewMissingEntity(sourceEntity: NodeEntity | NewMissingEntity): sourceEntity is NewMissingEntity {
return (sourceEntity as any).nodeId === undefined;
}
function findOrCreateSourceEntity(explicitEntities: NodeEntity[], sourceEntityKey: string): NodeEntity | NewMissingEntity {
const sourceEntity = explicitEntities.find((e) => e._key === sourceEntityKey);
if (sourceEntity === undefined) {
// This should never happen! The "sourceEntity" ought to exist in the integration.
return {
_key: sourceEntityKey,
};
} else {
return sourceEntity;
}
}
type NewPlaceholderEntity = TargetEntityProperties;
function isNewPlaceholderEntity(targetEntity: NodeEntity | NewPlaceholderEntity): targetEntity is NewPlaceholderEntity {
return (targetEntity as any).nodeId === undefined;
};
function findOrCreatePlaceholderEntity(nodeEntities: NodeEntity[], _mapping: RelationshipMapping): Entity | NewPlaceholderEntity {
const targetEntity = findTargetEntity(nodeEntities, _mapping);
if (targetEntity === undefined) {
return _mapping.targetEntity;
} else {
return targetEntity;
}
}
export function createMappedRelationshipNodesAndEdges(options: {
mappedRelationships: MappedRelationship[],
explicitEntities: Entity[],
}): MappedRelationshipNodesAndEdges {
const { mappedRelationships, explicitEntities } = options;
const mappedRelationshipNodes: Node[] = [];
const mappedRelationshipEdges: Edge[] = [];
const entities = [...explicitEntities];
const explicitNodeEntities: NodeEntity[] = [...explicitEntities.map((e) => ({ ...e, nodeId: getNodeIdFromEntity(e, [])}))];
const missingNodeEntities: NodeEntity[] = [];
const placeholderNodeEntities: NodeEntity[] = [];
for (const mappedRelationship of mappedRelationships) {
let sourceKey = explicitEntities.find((e) => e._key === mappedRelationship._mapping.sourceEntityKey)?._key;
if (sourceKey === undefined) {
// This should never happen! The "sourceEntity" ought to exist in the integration.
const sourceNode = createMissingEntity(mappedRelationship._mapping.sourceEntityKey);
const sourceEntity = findOrCreateSourceEntity([
...explicitNodeEntities,
...missingNodeEntities
], mappedRelationship._mapping.sourceEntityKey);
let sourceNodeId: string;
if (isNewMissingEntity(sourceEntity)) {
const missingSourceEntity = {
...sourceEntity,
nodeId: getNodeIdFromEntity(sourceEntity, [
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
]),
};
missingNodeEntities.push(missingSourceEntity);
const sourceNode = createMissingEntityNode(missingSourceEntity);
mappedRelationshipNodes.push(sourceNode);
sourceKey = sourceNode.id as string;
sourceNodeId = missingSourceEntity.nodeId;
} else {
sourceNodeId = getNodeIdFromEntity(sourceEntity, [
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
]);
}
let targetKey = entities.find((e) => isMatch(e, mappedRelationship._mapping.targetEntity))?._key;
const targetEntity = findOrCreatePlaceholderEntity([
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
], mappedRelationship._mapping);
let targetNodeId: string;
if (isNewPlaceholderEntity(targetEntity)) {
const placeholderTargetEntity = {
...targetEntity,
nodeId: getNodeIdFromEntity(targetEntity, [
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
]),
};
placeholderNodeEntities.push(placeholderTargetEntity);
if (targetKey === undefined) {
const targetNode = createPlaceholderEntity(mappedRelationship._mapping.targetEntity);
entities.push({
_key: targetNode.id as string,
...(mappedRelationship._mapping.targetEntity),
} as Entity);
const targetNode = createPlaceholderEntityNode(placeholderTargetEntity);
mappedRelationshipNodes.push(targetNode);
targetKey = targetNode.id
targetNodeId = placeholderTargetEntity.nodeId;
} else {
targetNodeId = getNodeIdFromEntity(targetEntity, [
...explicitNodeEntities,
...missingNodeEntities,
...placeholderNodeEntities
]);
}
mappedRelationshipEdges.push(createMappedRelationshipEdge(sourceKey, targetKey, mappedRelationship.displayName, mappedRelationship._mapping.relationshipDirection))
mappedRelationshipEdges.push(createMappedRelationshipEdge(sourceNodeId, targetNodeId, mappedRelationship.displayName, mappedRelationship._mapping.relationshipDirection))
}

@@ -48,17 +159,44 @@

const MISSING_GROUP = 'missing';
function createEntityNode(
node: Node,
options: {
isDuplicateId?: boolean,
labelHeader?: string,
labelBody?: string,
}
): Node {
const overrides: Partial<NodeOptions> = {};
const labelBody = options.labelBody || node.label;
if (options.isDuplicateId === true) {
options.labelHeader = '[DUPLICATE _KEY]' + options.labelHeader || '';
overrides.color = 'red';
}
function createMissingEntity(sourceEntityKey: string): Node {
if (options.labelHeader !== undefined) {
overrides.label = `<b>${options.labelHeader}</b>\n${labelBody}`;
overrides.font = { multi: 'html' };
}
return {
id: sourceEntityKey,
label: `<b>[MISSING ENTITY]</b>\n${sourceEntityKey}`,
color: 'red',
group: MISSING_GROUP,
font: {
// required: enables displaying <b>text</b> in the label as bold text
multi: 'html',
}
...node,
...overrides,
};
}
const MISSING_GROUP = 'missing';
function createMissingEntityNode(sourceEntity: NodeEntity): Node {
return createEntityNode(
{
id:sourceEntity.nodeId,
color: 'red',
group: MISSING_GROUP,
},
{
isDuplicateId: isNodeIdDuplicate(sourceEntity),
labelHeader: '[MISSING ENTITY]',
labelBody: sourceEntity._key,
},
);
}
const UNKNOWN_GROUP = 'unknown';

@@ -70,6 +208,9 @@

*/
function getWrappedJsonString(JSONableObject: object): string {
function getWrappedJsonString(options: {JSONableObject: object, keysToOmit: string[]}): string {
const { JSONableObject, keysToOmit } = options;
const keyValueArray: string[] = [];
for (const key of Object.keys(JSONableObject)) {
keyValueArray.push(`${key}: ${JSON.stringify(JSONableObject[key])}`);
if (!keysToOmit.includes(key)) {
keyValueArray.push(`${key}: ${JSON.stringify(JSONableObject[key])}`);
}
}

@@ -79,15 +220,21 @@ return keyValueArray.join('\n');

function createPlaceholderEntity(targetEntity: Partial<Entity>) {
return {
id: targetEntity._key || uuid(),
label: `<b>[PLACEHOLDER ENTITY]</b>\n${getWrappedJsonString(targetEntity)}`,
group: targetEntity._type || UNKNOWN_GROUP,
font: {
// required: enables displaying <b>text</b> in the label as bold text
multi: 'html',
}
}
function getWrappedEntityPropertiesString(nodeEntity: NodeEntity): string {
return getWrappedJsonString({JSONableObject: nodeEntity, keysToOmit: ['nodeId']})
}
export function createMappedRelationshipEdge(sourceKey: string, targetKey: string, label: string | undefined, relationshipDirection: RelationshipDirection): Edge {
function createPlaceholderEntityNode(targetEntity: NodeEntity) {
return createEntityNode(
{
id: targetEntity.nodeId,
group: targetEntity._type || UNKNOWN_GROUP,
},
{
isDuplicateId: isNodeIdDuplicate(targetEntity),
labelHeader: '[PLACEHOLDER ENTITY]',
labelBody: getWrappedEntityPropertiesString(targetEntity),
},
);
}
export function createMappedRelationshipEdge(sourceNodeId: string, targetNodeId: string, label: string | undefined, relationshipDirection: RelationshipDirection): Edge {
let fromKey: string;

@@ -97,8 +244,8 @@ let toKey: string;

case RelationshipDirection.FORWARD:
fromKey = sourceKey;
toKey = targetKey;
fromKey = sourceNodeId;
toKey = targetNodeId;
break;
case RelationshipDirection.REVERSE:
fromKey = targetKey;
toKey = sourceKey;
fromKey = targetNodeId;
toKey = sourceNodeId;
break;

@@ -105,0 +252,0 @@ }

@@ -12,2 +12,3 @@ import path from 'path';

import { createMappedRelationshipNodesAndEdges } from './createMappedRelationshipNodesAndEdges';
import { getNodeIdFromEntity } from './utils';

@@ -35,3 +36,3 @@ /**

const nodeDataSets = entities.map((entity) => ({
id: entity._key,
id: getNodeIdFromEntity(entity, []),
label: `${entity.displayName}\n[${entity._type}]`,

@@ -51,3 +52,6 @@ group: entity._type,

mappedRelationshipNodes,
} = createMappedRelationshipNodesAndEdges(mappedRelationships, entities);
} = createMappedRelationshipNodesAndEdges({
mappedRelationships,
explicitEntities: entities,
});

@@ -54,0 +58,0 @@ const htmlFileLocation = path.join(resolvedIntegrationPath, 'index.html');

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc