-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Geovanni Pacheco
committed
May 1, 2024
1 parent
d0f25b3
commit f3fb77e
Showing
108 changed files
with
28,376 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
packages/integration-sdk-core/docs/createIntegrationHelpers.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# createIntegrationHelpers | ||
|
||
This function is used to create typeful integration helpers . | ||
|
||
```typescript | ||
function createIntegrationHelpers( | ||
options: CreateIntegrationHelpersOptions, | ||
): IntegrationHelpers; | ||
``` | ||
|
||
#### Parameters | ||
|
||
- `options` - An object containing the following properties: | ||
- `integrationName`: Integration name. **_Example: 'aws', 'microsoft_365'_** | ||
- `classSchemaMap`: Typebox class schema map. | ||
|
||
#### Returns | ||
|
||
An object containing the following properties: | ||
|
||
- `createEntityType`: A function to generate entity types with | ||
`${integrationName}_${entityName}` pattern. | ||
- `createIntegrationEntity`: A function to create a entity metadata and a typed | ||
create entity function. | ||
|
||
#### Example | ||
|
||
```typescript | ||
import { createIntegrationHelpers } from '@jupiterone/integration-sdk-core'; | ||
import { classSchemaTypeboxMap } from '@jupiterone/data-model'; | ||
|
||
const { createEntityType, createIntegrationEntity } = createIntegrationHelpers({ | ||
integrationName: 'my_awesome_integration', | ||
schemaMap: classSchemaTypeboxMap, | ||
}); | ||
|
||
const [USER_ENTITY, createUserAssignEntity] = createIntegrationEntity({ | ||
resourceName: 'User', | ||
_class: ['User'], | ||
_type: createEntityType('user'), // This will generate "my_awesome_integration_user", but you are free to not use the createEntityType helper | ||
description: 'Entity description', // This will be used in the json schema | ||
schema: Type.Object({ | ||
name: Type.String(), | ||
}), | ||
}); | ||
|
||
// _type and _class will be generated automatically | ||
createUserAssignEntity({ | ||
_key: `${Entities.ACCOUNT._type}|${_integrationInstanceId}`, | ||
name, | ||
}); | ||
``` | ||
|
||
# How to use it in a current graph integration | ||
|
||
1. Create a file named helpers.ts in the src folder of your integration | ||
2. Add the following code to the file: | ||
|
||
```typescript | ||
import { createIntegrationHelpers } from '@jupiterone/integration-sdk-core'; | ||
import { classSchemaTypeboxMap } from '@jupiterone/data-model'; | ||
|
||
export const { createEntityType, createIntegrationEntity } = | ||
createIntegrationHelpers({ | ||
integrationName: INTEGRATION_NAME, | ||
schemaMap: classSchemaTypeboxMap, | ||
}); | ||
``` | ||
|
||
3. Replace INTEGRATION_NAME with the name of your integration | ||
4. Create a file named entities.ts in the src folder of your integration and add | ||
all entities as the following EXAMPLE code: | ||
|
||
```typescript | ||
import { createEntityType, createIntegrationEntity } from './helpers'; | ||
|
||
export const [UserEntityMetadata, createUserAssignEntity] = | ||
createIntegrationEntity({ | ||
resourceName: 'User', | ||
_class: ['User'], | ||
_type: createEntityType('user'), // This will generate `${INTEGRATION_NAME}_user`, but you are free to not use the createEntityType helper | ||
description: 'Entity description', // This will be used in the json schema | ||
schema: Type.Object({ | ||
name: Type.String(), | ||
}), | ||
}); | ||
``` | ||
|
||
5. Edit the constants.ts files and replace every entity with the new entities | ||
created in the entities.ts file as the following EXAMPLE code: | ||
|
||
```typescript | ||
import { UserEntityMetadata } from './entities'; | ||
|
||
export const Entities: Record< | ||
| 'USER' | ||
StepEntityMetadata | ||
> = { | ||
USER: UserEntityMetadata, | ||
}; | ||
``` | ||
|
||
6. Now you can go into every step converter and add your create entity helper to | ||
`createIntegrationEntity` assign property and have full autocomplete as the | ||
following EXAMPLE code: | ||
|
||
```diff | ||
export function createUserEntity(name: string): Entity { | ||
return createIntegrationEntity({ | ||
entityData: { | ||
source: {}, | ||
- assign: createUserAssignEntity({ | ||
+ assign: createUserAssignEntity({ | ||
_key: `${Entities.ACCOUNT._type}|${_integrationInstanceId}`, | ||
name, | ||
- }, | ||
+ }), | ||
}, | ||
}); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
packages/integration-sdk-core/src/data/__tests__/createIntegrationHelpers.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { Type } from '@sinclair/typebox'; | ||
import { SchemaMap } from '../../../tools/schemas'; | ||
import { createIntegrationHelpers } from '../createIntegrationHelpers'; | ||
import { EntityValidator } from '@jupiterone/integration-sdk-entity-validator'; | ||
import { entitySchemas } from '@jupiterone/data-model'; | ||
|
||
describe('createIntegrationHelpers', () => { | ||
const { createEntityType, createIntegrationEntity } = | ||
createIntegrationHelpers({ | ||
integrationName: 'test', | ||
classSchemaMap: SchemaMap, | ||
}); | ||
|
||
test('createEntityType', () => { | ||
expect(createEntityType('entity')).toBe('test_entity'); | ||
}); | ||
|
||
test('createIntegrationEntity createEntity', () => { | ||
const [, createEntity] = createIntegrationEntity({ | ||
resourceName: 'Entity', | ||
_class: ['Entity'], | ||
_type: 'entity', | ||
description: 'Entity description', | ||
schema: Type.Object({ | ||
id: Type.String(), | ||
name: Type.String(), | ||
}), | ||
}); | ||
|
||
expect( | ||
createEntity({ | ||
id: '1', | ||
name: 'entity', | ||
_key: 'id:123456', | ||
displayName: 'Entity', | ||
}), | ||
).toEqual({ | ||
id: '1', | ||
name: 'entity', | ||
_key: 'id:123456', | ||
displayName: 'Entity', | ||
_class: ['Entity'], | ||
_type: 'entity', | ||
}); | ||
}); | ||
|
||
test('createIntegrationEntity schema', () => { | ||
const [{ schema }] = createIntegrationEntity({ | ||
resourceName: 'Entity', | ||
_class: ['Entity'], | ||
_type: 'entity', | ||
description: 'Entity description', | ||
schema: Type.Object({ | ||
id: Type.String(), | ||
name: Type.String(), | ||
}), | ||
}); | ||
|
||
expect(schema).toEqual({ | ||
$id: '#entity', | ||
description: 'Entity description', | ||
allOf: [ | ||
{ $ref: '#Entity' }, | ||
{ | ||
properties: { | ||
id: { type: 'string' }, | ||
name: { type: 'string' }, | ||
_class: { | ||
type: 'array', | ||
items: [ | ||
{ | ||
const: 'Entity', | ||
type: 'string', | ||
}, | ||
], | ||
additionalItems: false, | ||
maxItems: 1, | ||
minItems: 1, | ||
}, | ||
_type: { const: 'entity', type: 'string' }, | ||
}, | ||
required: ['_class', '_type', 'id', 'name'], | ||
type: 'object', | ||
}, | ||
], | ||
}); | ||
}); | ||
|
||
test('createIntegrationEntity schema should validate createEntity return', () => { | ||
const validator = new EntityValidator({ | ||
schemas: Object.values(entitySchemas), | ||
}); | ||
const [{ schema }, createEntity] = createIntegrationEntity({ | ||
resourceName: 'Type', | ||
_class: ['Entity'], | ||
_type: 'type', | ||
description: 'Type description', | ||
schema: Type.Object({ | ||
id: Type.String(), | ||
name: Type.String(), | ||
}), | ||
}); | ||
validator.addSchemas(schema); | ||
|
||
const { errors } = validator.validateEntity( | ||
createEntity({ | ||
id: '1', | ||
name: 'type', | ||
_key: 'id:123456789', | ||
displayName: 'Type', | ||
}), | ||
); | ||
|
||
expect(errors).toEqual(null); | ||
}); | ||
}); |
84 changes: 84 additions & 0 deletions
84
packages/integration-sdk-core/src/data/createIntegrationHelpers.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Static, TObject, TRef, Type } from '@sinclair/typebox'; | ||
import { StepEntityMetadata } from '../types'; | ||
|
||
interface CreateIntegrationHelpersOptions< | ||
IntegrationName extends string, | ||
SchemaKey extends string, | ||
ClassSchemaMap extends Record<SchemaKey, TObject>, | ||
> { | ||
integrationName: IntegrationName; | ||
classSchemaMap: ClassSchemaMap; | ||
} | ||
|
||
export const createIntegrationHelpers = < | ||
IntegrationName extends string, | ||
SchemaKey extends string, | ||
ClassSchemaMap extends Record<SchemaKey, TObject>, | ||
>({ | ||
integrationName, | ||
classSchemaMap, | ||
}: CreateIntegrationHelpersOptions< | ||
IntegrationName, | ||
SchemaKey, | ||
ClassSchemaMap | ||
>) => { | ||
const createEntityType = <EntityName extends string>( | ||
entityName: EntityName, | ||
) => `${integrationName}_${entityName}` as const; | ||
|
||
const createIntegrationEntity = < | ||
ResourceName extends string, | ||
Class extends keyof ClassSchemaMap & string, | ||
EntityType extends string, | ||
Schema extends TObject, | ||
>({ | ||
resourceName, | ||
_class, | ||
_type, | ||
description, | ||
schema, | ||
...entityMetadata | ||
}: Omit<StepEntityMetadata, 'schema'> & { | ||
resourceName: ResourceName; | ||
_class: [Class, ...Class[]]; | ||
_type: EntityType; | ||
description: string; | ||
schema: Schema; | ||
}) => { | ||
const classSchemaRefs = _class.map((className) => | ||
Type.Ref(classSchemaMap[className]), | ||
) as [TRef<ClassSchemaMap[Class]>, ...TRef<ClassSchemaMap[Class]>[]]; | ||
|
||
const baseSchema = Type.Composite([ | ||
Type.Object({ | ||
_class: Type.Tuple(_class.map((className) => Type.Literal(className))), | ||
_type: Type.Literal(_type), | ||
}), | ||
schema, | ||
]); | ||
|
||
const entitySchema = Type.Intersect([...classSchemaRefs, baseSchema], { | ||
$id: `#${_type}`, | ||
description: description, | ||
}); | ||
type EntitySchemaType = Static<typeof entitySchema>; | ||
|
||
const createEntity = ( | ||
entityData: Omit<EntitySchemaType, '_class' | '_type'>, | ||
): EntitySchemaType => { | ||
return { ...entityData, _class: _class, _type: _type }; | ||
}; | ||
|
||
const stepEntityMetadata = { | ||
_class, | ||
_type, | ||
resourceName, | ||
schema: Type.Strict(entitySchema), | ||
...entityMetadata, | ||
} satisfies StepEntityMetadata; | ||
|
||
return [stepEntityMetadata, createEntity] as const; | ||
}; | ||
|
||
return { createEntityType, createIntegrationEntity }; | ||
}; |
Oops, something went wrong.