@jupiterone/integration-sdk-cli 8.3.1 → 8.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/commands/collect.d.ts +2 -0
- package/dist/src/commands/collect.js +50 -5
- package/dist/src/commands/collect.js.map +1 -1
- package/dist/src/commands/diff.js +3 -3
- package/dist/src/commands/diff.js.map +1 -1
- package/dist/src/commands/document.js +2 -2
- package/dist/src/commands/document.js.map +1 -1
- package/dist/src/commands/neo4j.js +7 -5
- package/dist/src/commands/neo4j.js.map +1 -1
- package/dist/src/commands/run.js +13 -13
- package/dist/src/commands/run.js.map +1 -1
- package/dist/src/commands/sync.js +7 -7
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/validate-question-file.js +4 -4
- package/dist/src/commands/validate-question-file.js.map +1 -1
- package/dist/src/commands/visualize-types.js +4 -4
- package/dist/src/commands/visualize-types.js.map +1 -1
- package/dist/src/commands/visualize.js +2 -2
- package/dist/src/commands/visualize.js.map +1 -1
- package/dist/src/config.js +3 -2
- package/dist/src/config.js.map +1 -1
- package/dist/src/index.js +10 -10
- package/dist/src/index.js.map +1 -1
- package/dist/src/log.js +1 -0
- package/dist/src/log.js.map +1 -1
- package/dist/src/neo4j/neo4jGraphStore.js +15 -14
- package/dist/src/neo4j/neo4jGraphStore.js.map +1 -1
- package/dist/src/neo4j/uploadToNeo4j.js +2 -2
- package/dist/src/neo4j/uploadToNeo4j.js.map +1 -1
- package/dist/src/neo4j/wipeNeo4j.js.map +1 -1
- package/dist/src/services/queryLanguage.js +1 -1
- package/dist/src/services/queryLanguage.js.map +1 -1
- package/dist/src/utils/generateVisHTML.js +1 -1
- package/dist/src/utils/generateVisHTML.js.map +1 -1
- package/dist/src/utils/getSortedJupiterOneTypes.js +2 -2
- package/dist/src/utils/getSortedJupiterOneTypes.js.map +1 -1
- package/dist/src/visualization/createMappedRelationshipNodesAndEdges.js +9 -9
- package/dist/src/visualization/createMappedRelationshipNodesAndEdges.js.map +1 -1
- package/dist/src/visualization/generateVisualization.js +6 -6
- package/dist/src/visualization/generateVisualization.js.map +1 -1
- package/dist/src/visualization/retrieveIntegrationData.js +1 -1
- package/dist/src/visualization/retrieveIntegrationData.js.map +1 -1
- package/dist/src/visualization/utils.d.ts +1 -1
- package/dist/src/visualization/utils.js +2 -2
- package/dist/src/visualization/utils.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -10704
- package/package.json +6 -4
- package/src/__tests__/cli.test.ts +78 -0
- package/src/commands/collect.ts +66 -2
- package/src/commands/document.ts +10 -9
- package/src/commands/neo4j.ts +16 -10
- package/src/commands/visualize-types.ts +7 -9
- package/src/config.ts +2 -1
- package/src/log.ts +1 -0
- package/src/neo4j/README.md +15 -18
- package/src/neo4j/__tests__/neo4jGraphStore.test.ts +55 -53
- package/src/neo4j/__tests__/neo4jUtilities.test.ts +27 -9
- package/src/neo4j/index.ts +1 -1
- package/src/neo4j/neo4jGraphStore.ts +39 -25
- package/src/neo4j/uploadToNeo4j.ts +5 -2
- package/src/neo4j/wipeNeo4j.ts +15 -11
- package/src/visualization/__tests__/createMappedRelationshipNodesAndEdges.test.ts +35 -49
- package/src/visualization/generateVisualization.ts +7 -12
- package/src/visualization/retrieveIntegrationData.ts +3 -5
- package/src/visualization/types/IntegrationData.ts +5 -1
- package/src/visualization/utils.ts +7 -4
- package/tsconfig.dist.json +1 -3
- package/tsconfig.json +1 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupiterone/integration-sdk-cli",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.2",
|
|
4
4
|
"description": "The SDK for developing JupiterOne integrations",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -22,9 +22,10 @@
|
|
|
22
22
|
"prepack": "yarn build:dist"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@jupiterone/integration-sdk-runtime": "^8.
|
|
25
|
+
"@jupiterone/integration-sdk-runtime": "^8.4.2",
|
|
26
26
|
"chalk": "^4",
|
|
27
27
|
"commander": "^5.0.0",
|
|
28
|
+
"fs-extra": "^10.0.0",
|
|
28
29
|
"globby": "^11.0.0",
|
|
29
30
|
"js-yaml": "^4.1.0",
|
|
30
31
|
"json-diff": "^0.5.4",
|
|
@@ -36,10 +37,11 @@
|
|
|
36
37
|
"vis": "^4.21.0-EOL"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@jupiterone/integration-sdk-private-test-utils": "^8.
|
|
40
|
+
"@jupiterone/integration-sdk-private-test-utils": "^8.4.2",
|
|
40
41
|
"@pollyjs/adapter-node-http": "^5.1.1",
|
|
41
42
|
"@pollyjs/core": "^5.1.1",
|
|
42
43
|
"@pollyjs/persister-fs": "^5.1.1",
|
|
44
|
+
"@types/fs-extra": "^9.0.13",
|
|
43
45
|
"@types/js-yaml": "^4.0.3",
|
|
44
46
|
"@types/json-diff": "^0.5.1",
|
|
45
47
|
"@types/lodash": "^4.14.158",
|
|
@@ -51,5 +53,5 @@
|
|
|
51
53
|
"neo-forgery": "^2.0.0",
|
|
52
54
|
"uuid": "^8.2.0"
|
|
53
55
|
},
|
|
54
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "1504c547765bd8636a8adcaf19098bc1b9f802e1"
|
|
55
57
|
}
|
|
@@ -48,6 +48,84 @@ describe('collect', () => {
|
|
|
48
48
|
loadProjectStructure('instanceWithDependentSteps');
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
test('option --no-cache requires option --step', async () => {
|
|
52
|
+
await expect(
|
|
53
|
+
createCli().parseAsync([
|
|
54
|
+
'node',
|
|
55
|
+
'j1-integration',
|
|
56
|
+
'collect',
|
|
57
|
+
'--no-cache',
|
|
58
|
+
]),
|
|
59
|
+
).rejects.toThrowError(
|
|
60
|
+
'Invalid option: Option --no-cache requires option --step to also be specified.',
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
test('option --cache-path requires option --step', async () => {
|
|
64
|
+
await expect(
|
|
65
|
+
createCli().parseAsync([
|
|
66
|
+
'node',
|
|
67
|
+
'j1-integration',
|
|
68
|
+
'collect',
|
|
69
|
+
'--cache-path',
|
|
70
|
+
'./',
|
|
71
|
+
]),
|
|
72
|
+
).rejects.toThrowError(
|
|
73
|
+
'Invalid option: Option --cache-path requires option --step to also be specified.',
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('Uses cache when --step flag is provided w/out --no-cache', async () => {
|
|
78
|
+
loadProjectStructure('instanceWithDependentCachedSteps');
|
|
79
|
+
|
|
80
|
+
await createCli().parseAsync([
|
|
81
|
+
'node',
|
|
82
|
+
'j1-integration',
|
|
83
|
+
'collect',
|
|
84
|
+
'--step',
|
|
85
|
+
'fetch-groups',
|
|
86
|
+
'--cache-path',
|
|
87
|
+
process.cwd(),
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
expect(log.displayExecutionResults).toHaveBeenCalledTimes(1);
|
|
91
|
+
expect(log.displayExecutionResults).toHaveBeenCalledWith({
|
|
92
|
+
integrationStepResults: [
|
|
93
|
+
{
|
|
94
|
+
id: 'fetch-accounts',
|
|
95
|
+
name: 'Fetch Accounts',
|
|
96
|
+
declaredTypes: ['my_account'],
|
|
97
|
+
dependsOn: undefined,
|
|
98
|
+
partialTypes: [],
|
|
99
|
+
encounteredTypes: ['test_account'],
|
|
100
|
+
status: StepResultStatus.CACHED,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'fetch-groups',
|
|
104
|
+
dependsOn: ['fetch-accounts'],
|
|
105
|
+
name: 'Fetch Groups',
|
|
106
|
+
declaredTypes: ['my_groups'],
|
|
107
|
+
partialTypes: [],
|
|
108
|
+
encounteredTypes: [],
|
|
109
|
+
status: StepResultStatus.SUCCESS,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'fetch-users',
|
|
113
|
+
name: 'Fetch Users',
|
|
114
|
+
declaredTypes: ['my_user'],
|
|
115
|
+
dependsOn: undefined,
|
|
116
|
+
partialTypes: [],
|
|
117
|
+
encounteredTypes: [],
|
|
118
|
+
status: StepResultStatus.DISABLED,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
metadata: {
|
|
122
|
+
partialDatasets: {
|
|
123
|
+
types: [],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
51
129
|
test('loads the integration, executes it, and logs the result', async () => {
|
|
52
130
|
await createCli().parseAsync(['node', 'j1-integration', 'collect']);
|
|
53
131
|
|
package/src/commands/collect.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createCommand } from 'commander';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import fs from 'fs-extra';
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
executeIntegrationLocally,
|
|
6
7
|
FileSystemGraphObjectStore,
|
|
8
|
+
getRootStorageDirectory,
|
|
7
9
|
prepareLocalStepCollection,
|
|
8
10
|
} from '@jupiterone/integration-sdk-runtime';
|
|
9
11
|
|
|
@@ -26,12 +28,31 @@ export function collect() {
|
|
|
26
28
|
)
|
|
27
29
|
.option(
|
|
28
30
|
'-s, --step <steps>',
|
|
29
|
-
'step(s) to run, comma separated',
|
|
31
|
+
'step(s) to run, comma separated. Utilizes available caches to speed up dependent steps.',
|
|
30
32
|
collector,
|
|
31
33
|
[],
|
|
32
34
|
)
|
|
35
|
+
.option(
|
|
36
|
+
'--no-cache',
|
|
37
|
+
'Can be used with the `--step` flag to disable the use of the cache.',
|
|
38
|
+
)
|
|
39
|
+
.option(
|
|
40
|
+
'--cache-path <filepath>',
|
|
41
|
+
'Can be used with the `--step` to specify a path to a non-default cache location.',
|
|
42
|
+
)
|
|
33
43
|
.option('-V, --disable-schema-validation', 'disable schema validation')
|
|
34
44
|
.action(async (options) => {
|
|
45
|
+
if (!options.cache && options.step.length === 0) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
'Invalid option: Option --no-cache requires option --step to also be specified.',
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (options.cachePath && options.step.length === 0) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
'Invalid option: Option --cache-path requires option --step to also be specified.',
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
35
56
|
// Point `fileSystem.ts` functions to expected location relative to
|
|
36
57
|
// integration project path.
|
|
37
58
|
process.env.JUPITERONE_INTEGRATION_STORAGE_DIRECTORY = path.resolve(
|
|
@@ -39,9 +60,21 @@ export function collect() {
|
|
|
39
60
|
'.j1-integration',
|
|
40
61
|
);
|
|
41
62
|
|
|
63
|
+
if (options.step.length > 0 && options.cache && !options.cachePath) {
|
|
64
|
+
// Step option was used, cache is wanted, and no cache path was provided
|
|
65
|
+
// therefore, copy .j1-integration into .j1-integration-cache
|
|
66
|
+
await buildCacheFromJ1Integration();
|
|
67
|
+
}
|
|
68
|
+
|
|
42
69
|
const config = prepareLocalStepCollection(
|
|
43
70
|
await loadConfig(path.join(options.projectPath, 'src')),
|
|
44
|
-
|
|
71
|
+
{
|
|
72
|
+
...options,
|
|
73
|
+
dependenciesCache: {
|
|
74
|
+
enabled: options.cache,
|
|
75
|
+
filepath: getRootCacheDirectory(options.cachePath),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
45
78
|
);
|
|
46
79
|
log.info('\nConfiguration loaded! Running integration...\n');
|
|
47
80
|
|
|
@@ -67,3 +100,34 @@ export function collect() {
|
|
|
67
100
|
log.displayExecutionResults(results);
|
|
68
101
|
});
|
|
69
102
|
}
|
|
103
|
+
|
|
104
|
+
export const DEFAULT_CACHE_DIRECTORY_NAME = '.j1-integration-cache';
|
|
105
|
+
|
|
106
|
+
export function getRootCacheDirectory(filepath?: string) {
|
|
107
|
+
return path.resolve(
|
|
108
|
+
typeof filepath === 'string' ? filepath : process.cwd(),
|
|
109
|
+
DEFAULT_CACHE_DIRECTORY_NAME,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Builds the step cache from the .j1-integration/graph directory
|
|
115
|
+
* by moving the files to .j1-integration-cache.
|
|
116
|
+
*/
|
|
117
|
+
async function buildCacheFromJ1Integration() {
|
|
118
|
+
const sourceGraphDirectory = path.join(getRootStorageDirectory(), 'graph');
|
|
119
|
+
const destinationGraphDirectory = path.join(getRootCacheDirectory(), 'graph');
|
|
120
|
+
|
|
121
|
+
const sourceExists = await fs.pathExists(sourceGraphDirectory);
|
|
122
|
+
if (sourceExists) {
|
|
123
|
+
await fs
|
|
124
|
+
.move(sourceGraphDirectory, destinationGraphDirectory, {
|
|
125
|
+
overwrite: true,
|
|
126
|
+
})
|
|
127
|
+
.catch((error) => {
|
|
128
|
+
log.error(`Failed to seed .j1-integration-cache from .j1-integration`);
|
|
129
|
+
log.error(error);
|
|
130
|
+
});
|
|
131
|
+
log.info(`Populated the .j1-integration-cache from .j1-integration.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/commands/document.ts
CHANGED
|
@@ -68,9 +68,8 @@ async function executeDocumentAction(
|
|
|
68
68
|
);
|
|
69
69
|
log.info('\nExisting documentation file successfully loaded!\n');
|
|
70
70
|
|
|
71
|
-
const newGeneratedDocumentationSection =
|
|
72
|
-
metadata
|
|
73
|
-
);
|
|
71
|
+
const newGeneratedDocumentationSection =
|
|
72
|
+
generateGraphObjectDocumentationFromStepsMetadata(metadata);
|
|
74
73
|
|
|
75
74
|
log.info('\nGenerated integration documentation section:');
|
|
76
75
|
log.info('---------------------------------------------\n');
|
|
@@ -187,9 +186,10 @@ ${generatedEntityTable}`;
|
|
|
187
186
|
}
|
|
188
187
|
|
|
189
188
|
if (metadata.relationships.length) {
|
|
190
|
-
const generatedRelationshipTable =
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
const generatedRelationshipTable =
|
|
190
|
+
generateRelationshipTableFromAllStepEntityMetadata(
|
|
191
|
+
metadata.relationships,
|
|
192
|
+
);
|
|
193
193
|
|
|
194
194
|
relationshipSection += `
|
|
195
195
|
|
|
@@ -201,9 +201,10 @@ ${generatedRelationshipTable}`;
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
if (metadata.mappedRelationships?.length) {
|
|
204
|
-
const generatedMappedRelationshipTable =
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
const generatedMappedRelationshipTable =
|
|
205
|
+
generateMappedRelationshipTableFromAllStepEntityMetadata(
|
|
206
|
+
metadata.mappedRelationships,
|
|
207
|
+
);
|
|
207
208
|
|
|
208
209
|
mappedRelationshipSection += `
|
|
209
210
|
|
package/src/commands/neo4j.ts
CHANGED
|
@@ -11,7 +11,9 @@ export function neo4j() {
|
|
|
11
11
|
dotenvExpand(dotenv.config());
|
|
12
12
|
|
|
13
13
|
const program = new commander.Command();
|
|
14
|
-
program.description(
|
|
14
|
+
program.description(
|
|
15
|
+
`Suite of neo4j commands. Options are currently 'neo4j push', 'neo4j wipe', and 'neo4j wipe-all'`,
|
|
16
|
+
);
|
|
15
17
|
const neo4jCommand = program.command('neo4j');
|
|
16
18
|
neo4jCommand
|
|
17
19
|
.command('push')
|
|
@@ -24,7 +26,7 @@ export function neo4j() {
|
|
|
24
26
|
.option(
|
|
25
27
|
'-i, --integration-instance-id <id>',
|
|
26
28
|
'_integrationInstanceId assigned to uploaded entities',
|
|
27
|
-
'defaultLocalInstanceID'
|
|
29
|
+
'defaultLocalInstanceID',
|
|
28
30
|
)
|
|
29
31
|
.action(async (options) => {
|
|
30
32
|
log.info(`Beginning data upload to local neo4j`);
|
|
@@ -34,30 +36,34 @@ export function neo4j() {
|
|
|
34
36
|
process.env.JUPITERONE_INTEGRATION_STORAGE_DIRECTORY = finalDir;
|
|
35
37
|
|
|
36
38
|
await uploadToNeo4j({
|
|
37
|
-
pathToData: finalDir,
|
|
38
|
-
integrationInstanceID: options.integrationInstanceId
|
|
39
|
+
pathToData: finalDir,
|
|
40
|
+
integrationInstanceID: options.integrationInstanceId,
|
|
39
41
|
});
|
|
40
42
|
log.info(`Data uploaded to local neo4j`);
|
|
41
43
|
});
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
neo4jCommand
|
|
44
46
|
.command('wipe')
|
|
45
|
-
.description(
|
|
47
|
+
.description(
|
|
48
|
+
'wipe entities and relationships for a given integrationInstanceID in the Neo4j database',
|
|
49
|
+
)
|
|
46
50
|
.option(
|
|
47
51
|
'-i, --integration-instance-id <id>',
|
|
48
52
|
'_integrationInstanceId assigned to uploaded entities',
|
|
49
|
-
'defaultLocalInstanceID'
|
|
53
|
+
'defaultLocalInstanceID',
|
|
50
54
|
)
|
|
51
55
|
.action(async (options) => {
|
|
52
|
-
await wipeNeo4jByID({
|
|
56
|
+
await wipeNeo4jByID({
|
|
57
|
+
integrationInstanceID: options.integrationInstanceId,
|
|
58
|
+
});
|
|
53
59
|
});
|
|
54
60
|
|
|
55
|
-
|
|
61
|
+
neo4jCommand
|
|
56
62
|
.command('wipe-all')
|
|
57
63
|
.description('wipe all entities and relationships in the Neo4j database')
|
|
58
64
|
.action(async (options) => {
|
|
59
65
|
await wipeAllNeo4j({});
|
|
60
66
|
});
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
return neo4jCommand;
|
|
63
69
|
}
|
|
@@ -138,15 +138,13 @@ export function getNodesAndEdgesFromStepMetadata(
|
|
|
138
138
|
types: options?.types,
|
|
139
139
|
},
|
|
140
140
|
);
|
|
141
|
-
const {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
},
|
|
149
|
-
);
|
|
141
|
+
const { placeholderEntityNodes, mappedRelationshipEdges } =
|
|
142
|
+
getNodesAndEdgesFromStepMappedRelationshipMetadata(
|
|
143
|
+
metadata.mappedRelationships || [],
|
|
144
|
+
{
|
|
145
|
+
types: options?.types,
|
|
146
|
+
},
|
|
147
|
+
);
|
|
150
148
|
const entityNodes = getNodesFromStepEntityMetadata(metadata.entities, {
|
|
151
149
|
types: options?.types,
|
|
152
150
|
edges: [...relationshipEdges, ...mappedRelationshipEdges],
|
package/src/config.ts
CHANGED
|
@@ -43,7 +43,8 @@ export function loadInvocationConfig(
|
|
|
43
43
|
integrationModule = require(path.resolve(projectSourceDirectory, 'index'));
|
|
44
44
|
} catch (err) {
|
|
45
45
|
throw new IntegrationInvocationConfigLoadError(
|
|
46
|
-
'Error loading integration invocation configuration. Ensure "invocationConfig" is exported from "src/index". Additional details: ' +
|
|
46
|
+
'Error loading integration invocation configuration. Ensure "invocationConfig" is exported from "src/index". Additional details: ' +
|
|
47
|
+
err,
|
|
47
48
|
);
|
|
48
49
|
}
|
|
49
50
|
|
package/src/log.ts
CHANGED
|
@@ -100,6 +100,7 @@ function logStepStatus(stepResult: IntegrationStepResult) {
|
|
|
100
100
|
function getStepStatusText(status: StepResultStatus) {
|
|
101
101
|
switch (status) {
|
|
102
102
|
case StepResultStatus.SUCCESS:
|
|
103
|
+
case StepResultStatus.CACHED:
|
|
103
104
|
return chalk.green(status);
|
|
104
105
|
case StepResultStatus.FAILURE:
|
|
105
106
|
return chalk.red(status);
|
package/src/neo4j/README.md
CHANGED
|
@@ -2,16 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Installation
|
|
4
4
|
|
|
5
|
-
This command assumes you have three additional values stored in your
|
|
6
|
-
|
|
7
|
-
NEO4J_URI
|
|
8
|
-
NEO4J_USER
|
|
9
|
-
NEO4J_PASSWORD
|
|
5
|
+
This command assumes you have three additional values stored in your local .env
|
|
6
|
+
file: NEO4J_URI NEO4J_USER NEO4J_PASSWORD
|
|
10
7
|
|
|
11
|
-
This can be used for uploading to local or remote Neo4j databases.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
This can be used for uploading to local or remote Neo4j databases. If SSL is
|
|
9
|
+
needed for a remote connection, specify `bolt+s` or `bolt+ssc` in the URI. For
|
|
10
|
+
easy access to a local Neo4j instance, you can launch one via a Neo4j provided
|
|
11
|
+
Docker image with the command:
|
|
15
12
|
|
|
16
13
|
```
|
|
17
14
|
docker run \
|
|
@@ -25,18 +22,18 @@ docker run \
|
|
|
25
22
|
neo4j:latest
|
|
26
23
|
```
|
|
27
24
|
|
|
28
|
-
If you would like to use a different username and password, the NEO4J_AUTH
|
|
29
|
-
|
|
25
|
+
If you would like to use a different username and password, the NEO4J_AUTH value
|
|
26
|
+
can be modified to whatever username/password you prefer.
|
|
30
27
|
|
|
31
|
-
NOTE:
|
|
32
|
-
|
|
28
|
+
NOTE: Future updates are planned to streamline this without removing the option
|
|
29
|
+
of pushing to an external Neo4j database.
|
|
33
30
|
|
|
34
31
|
## Usage
|
|
35
32
|
|
|
36
33
|
Data is still collected in the same way as before with a call to `yarn start`.
|
|
37
34
|
|
|
38
|
-
Once data has been collected, you can run `j1-integration neo4j push`.
|
|
39
|
-
push data to the Neo4j server listed in the NEO4J_URI .env parameter.
|
|
40
|
-
|
|
41
|
-
http://localhost:7474.
|
|
42
|
-
|
|
35
|
+
Once data has been collected, you can run `j1-integration neo4j push`. This will
|
|
36
|
+
push data to the Neo4j server listed in the NEO4J_URI .env parameter. If running
|
|
37
|
+
locally, you can then access data in the Neo4j database by visiting
|
|
38
|
+
http://localhost:7474. Alternatively, you can download the full Neo4j Desktop
|
|
39
|
+
application at https://neo4j.com/download/.
|
|
@@ -1,63 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
mockDriver,
|
|
3
|
-
mockSessionFromQuerySet,
|
|
4
|
-
QuerySpec
|
|
5
|
-
} from 'neo-forgery';
|
|
1
|
+
import { mockDriver, mockSessionFromQuerySet, QuerySpec } from 'neo-forgery';
|
|
6
2
|
import * as neo4j from 'neo4j-driver';
|
|
7
3
|
import { Neo4jGraphStore } from '../neo4jGraphStore';
|
|
8
4
|
import { Entity, Relationship } from '@jupiterone/integration-sdk-core';
|
|
9
5
|
|
|
10
6
|
const testInstanceID = 'testInstanceID';
|
|
11
|
-
const testEntityData: Entity[] = [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
const testEntityData: Entity[] = [
|
|
8
|
+
{
|
|
9
|
+
_type: 'testType',
|
|
10
|
+
_class: 'testClass',
|
|
11
|
+
_key: 'testKey',
|
|
12
|
+
},
|
|
13
|
+
];
|
|
14
|
+
const testRelationshipData: Relationship[] = [
|
|
15
|
+
{
|
|
16
|
+
_fromEntityKey: 'testKey1',
|
|
17
|
+
_toEntityKey: 'testKey2',
|
|
18
|
+
_type: 'testRelType',
|
|
19
|
+
_key: 'relKey',
|
|
20
|
+
_class: 'testRelationshipClass',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const constraintCall =
|
|
24
|
+
'CREATE CONSTRAINT unique_testType IF NOT EXISTS ON (n:testType) ASSERT n._key IS UNIQUE;';
|
|
24
25
|
const addEntityCall = `MERGE (n:testType {_key: 'testKey', _integrationInstanceID: '${testInstanceID}'}) SET n._type = 'testType', n._class = 'testClass';`;
|
|
25
26
|
const addRelationshipCall = `
|
|
26
27
|
MATCH (start {_key: 'testKey1', _integrationInstanceID: '${testInstanceID}'})
|
|
27
28
|
MATCH (end {_key: 'testKey2', _integrationInstanceID: '${testInstanceID}'})
|
|
28
|
-
MERGE (start)-[relationship:testRelType]->(end)
|
|
29
|
+
MERGE (start)-[relationship:testRelType]->(end);`;
|
|
29
30
|
const wipeByIDCall = `MATCH (n {_integrationInstanceID: '${testInstanceID}'}) DETACH DELETE n`;
|
|
30
31
|
const wipeAllCall = 'MATCH (n) DETACH DELETE n';
|
|
31
|
-
const querySet: QuerySpec[] = [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
32
|
+
const querySet: QuerySpec[] = [
|
|
33
|
+
{
|
|
34
|
+
name: 'addConstraint',
|
|
35
|
+
query: constraintCall,
|
|
36
|
+
params: undefined,
|
|
37
|
+
output: { records: [] },
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'addEntity',
|
|
41
|
+
query: addEntityCall,
|
|
42
|
+
params: undefined,
|
|
43
|
+
output: { records: [] },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'addRelationship',
|
|
47
|
+
query: addRelationshipCall,
|
|
48
|
+
params: undefined,
|
|
49
|
+
output: { records: [] },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'wipeByID',
|
|
53
|
+
query: wipeByIDCall,
|
|
54
|
+
params: undefined,
|
|
55
|
+
output: { records: [] },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'wipeAll',
|
|
59
|
+
query: wipeAllCall,
|
|
60
|
+
params: undefined,
|
|
61
|
+
output: { records: [] },
|
|
62
|
+
},
|
|
63
|
+
];
|
|
61
64
|
|
|
62
65
|
describe('#neo4jGraphStore', () => {
|
|
63
66
|
const mockDriverResp = mockDriver();
|
|
@@ -88,12 +91,11 @@ describe('#neo4jGraphStore', () => {
|
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
test('should generate call to create a Relationship', () => {
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
expect(async () => await store.addRelationships(testRelationshipData))
|
|
95
|
+
.toReturn;
|
|
93
96
|
});
|
|
94
97
|
|
|
95
98
|
test('should generate call to wipe by ID', () => {
|
|
96
99
|
expect(async () => await store.wipeInstanceIdData()).toReturn;
|
|
97
100
|
});
|
|
98
|
-
|
|
99
|
-
});
|
|
101
|
+
});
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
startsWithNumeric,
|
|
3
|
+
sanitizePropertyName,
|
|
4
|
+
sanitizeValue,
|
|
5
|
+
buildPropertyParameters,
|
|
6
|
+
} from '../neo4jUtilities';
|
|
3
7
|
|
|
4
8
|
describe('#neo4jUtilities', () => {
|
|
5
9
|
test('should return true for string starting with a numeric', () => {
|
|
@@ -11,15 +15,29 @@ describe('#neo4jUtilities', () => {
|
|
|
11
15
|
expect(testTrailingNumeric).toEqual(false);
|
|
12
16
|
});
|
|
13
17
|
test('should sanitize property name properly', () => {
|
|
14
|
-
const testSanitize: string = sanitizePropertyName(
|
|
15
|
-
|
|
18
|
+
const testSanitize: string = sanitizePropertyName(
|
|
19
|
+
`1a!b@c#d$e%f^g&h*i(j)k-l=m+n\\o|p'q"r;s:t/u?v.w,x>y<z\`1~2\t3\n4[5]6{7}8 90`,
|
|
20
|
+
);
|
|
21
|
+
expect(testSanitize).toEqual(
|
|
22
|
+
'n1a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_1_2_3_4_5_6_7_8_90',
|
|
23
|
+
);
|
|
16
24
|
});
|
|
17
25
|
test('should sanitize value properly', () => {
|
|
18
|
-
const testSanitize: string = sanitizeValue(
|
|
19
|
-
|
|
26
|
+
const testSanitize: string = sanitizeValue(
|
|
27
|
+
'1a!b@c#d$e%f^g&h*i(j)k-l=m+n\\o|p\'q"r;s:t/u?v.w,x>y<z`1~2\t3\n4[5]6{7}8 90',
|
|
28
|
+
);
|
|
29
|
+
expect(testSanitize).toEqual(
|
|
30
|
+
'1a!b@c#d$e%f^g&h*i(j)k-l=m+n\\o|p\'q\\"r;s:t/u?v.w,x>y<z`1~2\t3\n4[5]6{7}8 90',
|
|
31
|
+
);
|
|
20
32
|
});
|
|
21
33
|
test('should build property string correctly including sanitization', () => {
|
|
22
|
-
const testPropResults: Object = buildPropertyParameters({
|
|
23
|
-
|
|
34
|
+
const testPropResults: Object = buildPropertyParameters({
|
|
35
|
+
test: '123',
|
|
36
|
+
'1sanitize1hi&$abc d': '1h"i&$abc d',
|
|
37
|
+
});
|
|
38
|
+
expect(testPropResults).toEqual({
|
|
39
|
+
test: '123',
|
|
40
|
+
n1sanitize1hi__abc_d: '1h\\"i&$abc d',
|
|
41
|
+
});
|
|
24
42
|
});
|
|
25
|
-
});
|
|
43
|
+
});
|
package/src/neo4j/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './uploadToNeo4j';
|
|
2
|
-
export * from './wipeNeo4j';
|
|
2
|
+
export * from './wipeNeo4j';
|