@jupiterone/integration-sdk-cli 8.17.0 → 8.19.0

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.
@@ -0,0 +1,228 @@
1
+ import {
2
+ IntegrationInstanceConfig,
3
+ IntegrationStep,
4
+ RelationshipClass,
5
+ RelationshipDirection,
6
+ } from '@jupiterone/integration-sdk-core';
7
+ import { getMockIntegrationStep } from '@jupiterone/integration-sdk-private-test-utils';
8
+ import { generateIntegrationGraphSchema } from './generate-integration-graph-schema';
9
+
10
+ describe('#generateIntegrationGraphSchema', () => {
11
+ test('should return aggregate schema from steps', () => {
12
+ const steps: IntegrationStep<IntegrationInstanceConfig>[] = [
13
+ getMockIntegrationStep({
14
+ entities: [
15
+ {
16
+ _class: 'User',
17
+ _type: 'my_user',
18
+ resourceName: 'The user',
19
+ },
20
+ {
21
+ _class: 'Group',
22
+ _type: 'my_group',
23
+ resourceName: 'The group',
24
+ },
25
+ ],
26
+ mappedRelationships: [
27
+ {
28
+ _class: RelationshipClass.IS,
29
+ sourceType: 'my_user',
30
+ targetType: 'employee',
31
+ direction: RelationshipDirection.FORWARD,
32
+ _type: 'my_user_is_employee',
33
+ },
34
+ ],
35
+ }),
36
+ getMockIntegrationStep({
37
+ entities: [
38
+ {
39
+ _class: 'Group',
40
+ _type: 'my_group',
41
+ resourceName: 'The group',
42
+ },
43
+ ],
44
+ relationships: [
45
+ {
46
+ _class: RelationshipClass.HAS,
47
+ _type: 'my_group_has_user',
48
+ sourceType: 'my_user',
49
+ targetType: 'my_group',
50
+ },
51
+ ],
52
+ mappedRelationships: [],
53
+ }),
54
+ ];
55
+
56
+ expect(generateIntegrationGraphSchema(steps)).toEqual({
57
+ entities: [
58
+ {
59
+ _class: 'User',
60
+ _type: 'my_user',
61
+ resourceName: 'The user',
62
+ },
63
+ {
64
+ _class: 'Group',
65
+ _type: 'my_group',
66
+ resourceName: 'The group',
67
+ },
68
+ ],
69
+ relationships: [
70
+ {
71
+ _class: RelationshipClass.HAS,
72
+ sourceType: 'my_user',
73
+ targetType: 'my_group',
74
+ },
75
+ ],
76
+ mappedRelationships: [
77
+ {
78
+ _class: RelationshipClass.IS,
79
+ sourceType: 'my_user',
80
+ targetType: 'employee',
81
+ direction: RelationshipDirection.FORWARD,
82
+ },
83
+ ],
84
+ });
85
+ });
86
+
87
+ test('should dedup entity metadata', () => {
88
+ const steps: IntegrationStep<IntegrationInstanceConfig>[] = [
89
+ getMockIntegrationStep({
90
+ entities: [
91
+ {
92
+ _class: 'User',
93
+ _type: 'my_user',
94
+ resourceName: 'The user',
95
+ },
96
+ {
97
+ _class: 'User',
98
+ _type: 'my_user',
99
+ resourceName: 'The user',
100
+ },
101
+ ],
102
+ relationships: [],
103
+ mappedRelationships: [],
104
+ }),
105
+ getMockIntegrationStep({
106
+ entities: [
107
+ {
108
+ _class: 'User',
109
+ _type: 'my_user',
110
+ resourceName: 'The user',
111
+ },
112
+ ],
113
+ relationships: [],
114
+ mappedRelationships: [],
115
+ }),
116
+ ];
117
+
118
+ expect(generateIntegrationGraphSchema(steps)).toEqual({
119
+ entities: [
120
+ {
121
+ _class: 'User',
122
+ _type: 'my_user',
123
+ resourceName: 'The user',
124
+ },
125
+ ],
126
+ relationships: [],
127
+ mappedRelationships: [],
128
+ });
129
+ });
130
+
131
+ test('should dedup relationship metadata', () => {
132
+ const steps: IntegrationStep<IntegrationInstanceConfig>[] = [
133
+ getMockIntegrationStep({
134
+ entities: [],
135
+ relationships: [
136
+ {
137
+ _class: RelationshipClass.HAS,
138
+ _type: 'my_group_has_user',
139
+ sourceType: 'my_user',
140
+ targetType: 'my_group',
141
+ },
142
+ {
143
+ _class: RelationshipClass.HAS,
144
+ _type: 'my_group_has_user',
145
+ sourceType: 'my_user',
146
+ targetType: 'my_group',
147
+ },
148
+ ],
149
+ mappedRelationships: [],
150
+ }),
151
+ getMockIntegrationStep({
152
+ entities: [],
153
+ relationships: [
154
+ {
155
+ _class: RelationshipClass.HAS,
156
+ _type: 'my_group_has_user',
157
+ sourceType: 'my_user',
158
+ targetType: 'my_group',
159
+ },
160
+ ],
161
+ mappedRelationships: [],
162
+ }),
163
+ ];
164
+
165
+ expect(generateIntegrationGraphSchema(steps)).toEqual({
166
+ entities: [],
167
+ relationships: [
168
+ {
169
+ _class: RelationshipClass.HAS,
170
+ sourceType: 'my_user',
171
+ targetType: 'my_group',
172
+ },
173
+ ],
174
+ mappedRelationships: [],
175
+ });
176
+ });
177
+
178
+ test('should dedup mapped relationship metadata', () => {
179
+ const steps: IntegrationStep<IntegrationInstanceConfig>[] = [
180
+ getMockIntegrationStep({
181
+ entities: [],
182
+ relationships: [],
183
+ mappedRelationships: [
184
+ {
185
+ _class: RelationshipClass.IS,
186
+ sourceType: 'my_user',
187
+ targetType: 'employee',
188
+ direction: RelationshipDirection.FORWARD,
189
+ _type: 'my_user_is_employee',
190
+ },
191
+ {
192
+ _class: RelationshipClass.IS,
193
+ sourceType: 'my_user',
194
+ targetType: 'employee',
195
+ direction: RelationshipDirection.FORWARD,
196
+ _type: 'my_user_is_employee',
197
+ },
198
+ ],
199
+ }),
200
+ getMockIntegrationStep({
201
+ entities: [],
202
+ relationships: [],
203
+ mappedRelationships: [
204
+ {
205
+ _class: RelationshipClass.IS,
206
+ sourceType: 'my_user',
207
+ targetType: 'employee',
208
+ direction: RelationshipDirection.FORWARD,
209
+ _type: 'my_user_is_employee',
210
+ },
211
+ ],
212
+ }),
213
+ ];
214
+
215
+ expect(generateIntegrationGraphSchema(steps)).toEqual({
216
+ entities: [],
217
+ relationships: [],
218
+ mappedRelationships: [
219
+ {
220
+ _class: RelationshipClass.IS,
221
+ sourceType: 'my_user',
222
+ targetType: 'employee',
223
+ direction: RelationshipDirection.FORWARD,
224
+ },
225
+ ],
226
+ });
227
+ });
228
+ });
@@ -0,0 +1,226 @@
1
+ import {
2
+ IntegrationInstanceConfig,
3
+ IntegrationStepExecutionContext,
4
+ Step,
5
+ StepEntityMetadata,
6
+ StepMappedRelationshipMetadata,
7
+ StepRelationshipMetadata,
8
+ } from '@jupiterone/integration-sdk-core';
9
+ import { createCommand } from 'commander';
10
+ import path from 'path';
11
+ import { loadConfig } from '../config';
12
+ import { promises as fs } from 'fs';
13
+ import * as log from '../log';
14
+
15
+ export function generateIntegrationGraphSchemaCommand() {
16
+ return createCommand('generate-integration-graph-schema')
17
+ .description(
18
+ 'generate integration graph metadata summary from step metadata',
19
+ )
20
+ .option(
21
+ '-o, --output-file <path>',
22
+ 'project relative path to generated integration graph schema file',
23
+ )
24
+ .option(
25
+ '-p, --project-path <directory>',
26
+ 'path to integration project directory',
27
+ process.cwd(),
28
+ )
29
+ .action(async (options) => {
30
+ const { projectPath, outputFile } = options;
31
+
32
+ log.info(
33
+ `Generating integration graph schema (projectPath=${projectPath}, outputFile=${outputFile})`,
34
+ );
35
+ const config = await loadConfig(path.join(projectPath, 'src'));
36
+
37
+ const integrationGraphSchema = generateIntegrationGraphSchema(
38
+ config.integrationSteps,
39
+ );
40
+
41
+ if (outputFile) {
42
+ await fs.writeFile(outputFile, JSON.stringify(integrationGraphSchema), {
43
+ encoding: 'utf-8',
44
+ });
45
+ } else {
46
+ console.log(JSON.stringify(integrationGraphSchema, null, 2));
47
+ }
48
+
49
+ log.info('Successfully generated integration graph schema');
50
+ });
51
+ }
52
+
53
+ type IntegrationGraphSchemaEntityMetadata = {
54
+ resourceName: string;
55
+ _class: string | string[];
56
+ _type: string;
57
+ };
58
+
59
+ type IntegrationGraphSchemaRelationshipMetadata = {
60
+ _class: string;
61
+ sourceType: string;
62
+ targetType: string;
63
+ };
64
+
65
+ type IntegrationGraphSchemaMappedRelationshipMetadata = {
66
+ sourceType: string;
67
+ _class: string;
68
+ targetType: string;
69
+ direction: 'FORWARD' | 'REVERSE';
70
+ };
71
+
72
+ type IntegrationGraphSchema = {
73
+ entities: IntegrationGraphSchemaEntityMetadata[];
74
+ relationships: IntegrationGraphSchemaRelationshipMetadata[];
75
+ mappedRelationships: IntegrationGraphSchemaMappedRelationshipMetadata[];
76
+ };
77
+
78
+ export function generateIntegrationGraphSchema(
79
+ integrationSteps: Step<
80
+ IntegrationStepExecutionContext<IntegrationInstanceConfig, object>
81
+ >[],
82
+ ): IntegrationGraphSchema {
83
+ const integrationGraphSchema: IntegrationGraphSchema = {
84
+ entities: [],
85
+ relationships: [],
86
+ mappedRelationships: [],
87
+ };
88
+
89
+ // To ensure that we are generating a unique set of metadata, we dedup any
90
+ // entity, relationship, and mapped relationship schemas that may be
91
+ // duplicated across steps (rare, but possible).
92
+ const uniqueEntitySchemaSet = new Set<string>();
93
+ const uniqueRelationshipSchemaSet = new Set<string>();
94
+ const uniqueMappedRelationshipSchemaSet = new Set<string>();
95
+
96
+ function getIntegrationGraphSchemaEntityMetadataForStep(
97
+ stepEntityMetadata: StepEntityMetadata[],
98
+ ): IntegrationGraphSchemaEntityMetadata[] {
99
+ const entities: IntegrationGraphSchemaEntityMetadata[] = [];
100
+
101
+ for (const entityMetadata of stepEntityMetadata) {
102
+ const entitySchemaKey = getEntitySchemaKey(entityMetadata);
103
+
104
+ if (uniqueEntitySchemaSet.has(entitySchemaKey)) continue;
105
+ uniqueEntitySchemaSet.add(entitySchemaKey);
106
+
107
+ entities.push(toIntegrationGraphSchemaEntityMetadata(entityMetadata));
108
+ }
109
+
110
+ return entities;
111
+ }
112
+
113
+ function getIntegrationGraphSchemaRelationshipMetadataForStep(
114
+ stepRelationshipMetadata: StepRelationshipMetadata[],
115
+ ) {
116
+ const relationships: IntegrationGraphSchemaRelationshipMetadata[] = [];
117
+
118
+ for (const relationshipMetadata of stepRelationshipMetadata) {
119
+ const relationshipSchemaKey =
120
+ getRelationshipSchemaKey(relationshipMetadata);
121
+
122
+ if (uniqueRelationshipSchemaSet.has(relationshipSchemaKey)) continue;
123
+ uniqueRelationshipSchemaSet.add(relationshipSchemaKey);
124
+
125
+ relationships.push(
126
+ toIntegrationGraphSchemaRelationshipMetadata(relationshipMetadata),
127
+ );
128
+ }
129
+
130
+ return relationships;
131
+ }
132
+
133
+ function getIntegrationGraphSchemaMappedRelationshipMetadataForStep(
134
+ stepMappedRelationshipMetadata: StepMappedRelationshipMetadata[],
135
+ ) {
136
+ const mappedRelationships: IntegrationGraphSchemaMappedRelationshipMetadata[] =
137
+ [];
138
+
139
+ for (const mappedRelationshipMetadata of stepMappedRelationshipMetadata) {
140
+ const mappedRelationshipSchemaKey = getMappedRelationshipSchemaKey(
141
+ mappedRelationshipMetadata,
142
+ );
143
+
144
+ if (uniqueMappedRelationshipSchemaSet.has(mappedRelationshipSchemaKey))
145
+ continue;
146
+ uniqueMappedRelationshipSchemaSet.add(mappedRelationshipSchemaKey);
147
+
148
+ mappedRelationships.push(
149
+ toIntegrationGraphSchemaMappedRelationshipMetadata(
150
+ mappedRelationshipMetadata,
151
+ ),
152
+ );
153
+ }
154
+
155
+ return mappedRelationships;
156
+ }
157
+
158
+ for (const step of integrationSteps) {
159
+ integrationGraphSchema.entities = integrationGraphSchema.entities.concat(
160
+ getIntegrationGraphSchemaEntityMetadataForStep(step.entities),
161
+ );
162
+
163
+ integrationGraphSchema.relationships =
164
+ integrationGraphSchema.relationships.concat(
165
+ getIntegrationGraphSchemaRelationshipMetadataForStep(
166
+ step.relationships,
167
+ ),
168
+ );
169
+
170
+ if (step.mappedRelationships) {
171
+ integrationGraphSchema.mappedRelationships =
172
+ integrationGraphSchema.mappedRelationships.concat(
173
+ getIntegrationGraphSchemaMappedRelationshipMetadataForStep(
174
+ step.mappedRelationships,
175
+ ),
176
+ );
177
+ }
178
+ }
179
+
180
+ return integrationGraphSchema;
181
+ }
182
+
183
+ function getEntitySchemaKey(metadata: StepEntityMetadata) {
184
+ return `${metadata._class}|${metadata._type}`;
185
+ }
186
+
187
+ function getRelationshipSchemaKey(metadata: StepRelationshipMetadata) {
188
+ return `${metadata.sourceType}|${metadata._class}|${metadata.targetType}`;
189
+ }
190
+
191
+ function getMappedRelationshipSchemaKey(
192
+ metadata: StepMappedRelationshipMetadata,
193
+ ) {
194
+ return `${metadata.sourceType}|${metadata._class}|${metadata.targetType}|${metadata.direction}`;
195
+ }
196
+
197
+ function toIntegrationGraphSchemaEntityMetadata(
198
+ stepEntityMetadata: StepEntityMetadata,
199
+ ): IntegrationGraphSchemaEntityMetadata {
200
+ return {
201
+ resourceName: stepEntityMetadata.resourceName,
202
+ _class: stepEntityMetadata._class,
203
+ _type: stepEntityMetadata._type,
204
+ };
205
+ }
206
+
207
+ function toIntegrationGraphSchemaRelationshipMetadata(
208
+ relationshipMetadata: StepRelationshipMetadata,
209
+ ): IntegrationGraphSchemaRelationshipMetadata {
210
+ return {
211
+ _class: relationshipMetadata._class,
212
+ sourceType: relationshipMetadata.sourceType,
213
+ targetType: relationshipMetadata.targetType,
214
+ };
215
+ }
216
+
217
+ function toIntegrationGraphSchemaMappedRelationshipMetadata(
218
+ mappedRelationshipMetadata: StepMappedRelationshipMetadata,
219
+ ): IntegrationGraphSchemaMappedRelationshipMetadata {
220
+ return {
221
+ sourceType: mappedRelationshipMetadata.sourceType,
222
+ _class: mappedRelationshipMetadata._class,
223
+ targetType: mappedRelationshipMetadata.targetType,
224
+ direction: mappedRelationshipMetadata.direction,
225
+ };
226
+ }
@@ -8,3 +8,4 @@ export * from './visualize-types';
8
8
  export * from './validate-question-file';
9
9
  export * from './neo4j';
10
10
  export * from './visualize-dependencies';
11
+ export * from './generate-integration-graph-schema';
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  validateQuestionFile,
12
12
  neo4j,
13
13
  visualizeDependencies,
14
+ generateIntegrationGraphSchemaCommand,
14
15
  } from './commands';
15
16
 
16
17
  export function createCli() {
@@ -24,5 +25,6 @@ export function createCli() {
24
25
  .addCommand(document())
25
26
  .addCommand(validateQuestionFile())
26
27
  .addCommand(neo4j())
27
- .addCommand(visualizeDependencies());
28
+ .addCommand(visualizeDependencies())
29
+ .addCommand(generateIntegrationGraphSchemaCommand());
28
30
  }
package/src/log.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import chalk from 'chalk';
2
2
 
3
- import { ExecuteIntegrationResult } from '@jupiterone/integration-sdk-runtime';
3
+ import {
4
+ ExecuteIntegrationResult,
5
+ processDeclaredTypesDiff,
6
+ } from '@jupiterone/integration-sdk-runtime';
4
7
  import {
5
8
  IntegrationStepResult,
6
9
  StepResultStatus,
@@ -36,29 +39,17 @@ export function displayExecutionResults(results: ExecuteIntegrationResult) {
36
39
 
37
40
  let undeclaredTypesDetected: boolean = false;
38
41
 
39
- results.integrationStepResults.forEach((step) => {
42
+ processDeclaredTypesDiff(results, (step, undeclaredTypes) => {
40
43
  logStepStatus(step);
41
44
 
42
- if (step.status === StepResultStatus.SUCCESS) {
43
- const { declaredTypes, encounteredTypes } = step;
44
-
45
- const declaredTypeSet = new Set(declaredTypes);
46
- const undeclaredTypes = encounteredTypes.filter(
47
- (type) => !declaredTypeSet.has(type),
45
+ if (step.status === StepResultStatus.SUCCESS && undeclaredTypes.length) {
46
+ undeclaredTypesDetected = true;
47
+ const undeclaredTypesList = undeclaredTypes.map((type) => {
48
+ return `\n - ${type}`;
49
+ });
50
+ warn(
51
+ `The following types were encountered but are not declared in the step's "types" field:${undeclaredTypesList}`,
48
52
  );
49
-
50
- if (undeclaredTypes.length) {
51
- undeclaredTypesDetected = true;
52
-
53
- const undeclaredTypesList = undeclaredTypes.map((type) => {
54
- return `\n - ${type}`;
55
- });
56
- warn(
57
- `The following types were encountered but are not declared in the step's "types" field:${undeclaredTypesList}`,
58
- );
59
-
60
- console.log('');
61
- }
62
53
  }
63
54
  });
64
55