@devrev/ts-adaas 0.0.1
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/README.md +163 -0
- package/dist/src/adapter/helpers.d.ts +4 -0
- package/dist/src/adapter/helpers.js +47 -0
- package/dist/src/adapter/index.d.ts +53 -0
- package/dist/src/adapter/index.js +110 -0
- package/dist/src/adapter/index.test.d.ts +1 -0
- package/dist/src/adapter/index.test.js +105 -0
- package/dist/src/demo-extractor/index.d.ts +4 -0
- package/dist/src/demo-extractor/index.js +149 -0
- package/dist/src/demo-extractor/recipe.json +37 -0
- package/dist/src/http/client.d.ts +16 -0
- package/dist/src/http/client.js +147 -0
- package/dist/src/http/constants.d.ts +3 -0
- package/dist/src/http/constants.js +6 -0
- package/dist/src/http/index.d.ts +3 -0
- package/dist/src/http/index.js +19 -0
- package/dist/src/http/types.d.ts +12 -0
- package/dist/src/http/types.js +2 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +20 -0
- package/dist/src/logging/index.d.ts +31 -0
- package/dist/src/logging/index.js +60 -0
- package/dist/src/types/common.d.ts +33 -0
- package/dist/src/types/common.js +9 -0
- package/dist/src/types/extraction.d.ts +105 -0
- package/dist/src/types/extraction.js +67 -0
- package/dist/src/types/index.d.ts +2 -0
- package/dist/src/types/index.js +18 -0
- package/dist/src/uploader/index.d.ts +32 -0
- package/dist/src/uploader/index.js +95 -0
- package/dist/tests/adapter.test.d.ts +1 -0
- package/dist/tests/adapter.test.js +73 -0
- package/dist/tests/demo-extractor.test.d.ts +1 -0
- package/dist/tests/demo-extractor.test.js +97 -0
- package/dist/tests/helpers.test.d.ts +1 -0
- package/dist/tests/helpers.test.js +38 -0
- package/dist/tests/test-helpers.d.ts +2 -0
- package/dist/tests/test-helpers.js +33 -0
- package/dist/tests/types.test.d.ts +1 -0
- package/dist/tests/types.test.js +71 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# ADaaS Library
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# ADaaS Library
|
|
5
|
+
|
|
6
|
+
Typescript ADaaS Library (@devrev/ts-adaas) provides:
|
|
7
|
+
|
|
8
|
+
- type definitions for ADaaS control protocol,
|
|
9
|
+
- an adapter for ADaaS control protocol,
|
|
10
|
+
- helpers for uploading artifacts and manage the state for ADaaS snap-in.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
Create a new ADaaS adapter on each ADaaS snap-in invocation:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
const adapter = new Adapter(event: AirdropEvent);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Adapter class provides:
|
|
21
|
+
|
|
22
|
+
- helper function to emit response,
|
|
23
|
+
- automatic emit event if ADaaS snap-in invocation runs out of time,
|
|
24
|
+
- setter for updating ADaaS snap-in state and adding artifacts to the return ADaaS message.
|
|
25
|
+
|
|
26
|
+
### Phases of Airdrop Extraction
|
|
27
|
+
|
|
28
|
+
Each ADaaS snap-in must handle all the phases of ADaaS extraction.
|
|
29
|
+
|
|
30
|
+
ADaaS library provides type definitions to ensure ADaaS snap-ins are compatible with ADaaS control protocol.
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
async run() {
|
|
34
|
+
switch (this.event.payload.event_type) {
|
|
35
|
+
case EventType.ExtractionExternalSyncUnitsStart: {
|
|
36
|
+
|
|
37
|
+
// extract available External Sync Units (projects, organizations, ...)
|
|
38
|
+
|
|
39
|
+
await this.adapter.emit(ExtractorEventType.ExtractionExternalSyncUnitsDone, {
|
|
40
|
+
external_sync_units: externalSyncUnits,
|
|
41
|
+
});
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case EventType.ExtractionMetadataStart: {
|
|
46
|
+
|
|
47
|
+
// provide mappings of domain objects by provioding initial_domain_mapping.json file
|
|
48
|
+
// update ADaaS snap-in state
|
|
49
|
+
|
|
50
|
+
await this.adapter.emit(ExtractorEventType.ExtractionMetadataDone);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
case EventType.ExtractionDataStart: {
|
|
55
|
+
|
|
56
|
+
// extract Data
|
|
57
|
+
// upload Data
|
|
58
|
+
// update ADaaS snap-in state
|
|
59
|
+
// approximate progress done
|
|
60
|
+
|
|
61
|
+
await this.adapter.emit(ExtractorEventType.ExtractionDataContinue, {
|
|
62
|
+
progress: 10,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case EventType.ExtractionDataContinue: {
|
|
69
|
+
await this.processExtractionData();
|
|
70
|
+
|
|
71
|
+
// extract Data
|
|
72
|
+
// upload Data
|
|
73
|
+
// update ADaaS snap-in state
|
|
74
|
+
// approximate progress done
|
|
75
|
+
|
|
76
|
+
await this.adapter.emit(ExtractorEventType.ExtractionDataDone, {
|
|
77
|
+
progress: 100,
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case EventType.ExtractionDataDelete: {
|
|
83
|
+
|
|
84
|
+
// if an extraction has any side-effects to 3rd party systems cleanup should be done here.
|
|
85
|
+
|
|
86
|
+
await this.adapter.emit(ExtractorEventType.ExtractionDataDeleteDone);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case EventType.ExtractionAttachmentsStart: {
|
|
91
|
+
|
|
92
|
+
// extract Attachments
|
|
93
|
+
// upload Attachments
|
|
94
|
+
// update ADaaS snap-in state
|
|
95
|
+
|
|
96
|
+
await this.adapter.emit(ExtractorEventType.ExtractionAttachmentsContinue);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
case EventType.ExtractionAttachmentsContinue: {
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
// extract Attachments
|
|
104
|
+
// upload Attachments
|
|
105
|
+
// update ADaaS snap-in state
|
|
106
|
+
|
|
107
|
+
await this.adapter.emit(ExtractorEventType.ExtractionAttachmentsDone);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
case EventType.ExtractionAttachmentsDelete: {
|
|
112
|
+
|
|
113
|
+
// if an extraction has any side-effects to 3rd party systems cleanup should be done here.
|
|
114
|
+
|
|
115
|
+
await this.adapter.emit(ExtractorEventType.ExtractionAttachmentsDeleteDone);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
default: {
|
|
120
|
+
console.log('Event not supported' + JSON.stringify(this.event));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Uploading artifacts
|
|
127
|
+
|
|
128
|
+
Create a new Uploader class for uploading artifacts:
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
const upload = new Uploader(
|
|
132
|
+
event.execution_metadata.devrev_endpoint,
|
|
133
|
+
event.context.secrets.service_account_token
|
|
134
|
+
);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Files with extracted domain objects must be in JSONL (JSON Lines) format. Data files should contain 2000 - 5000 records each.
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
const entity = 'users';
|
|
141
|
+
const { artifact, error } = await this.uploader.upload(
|
|
142
|
+
`extractor_${entity}_${i}.jsonl`,
|
|
143
|
+
entity,
|
|
144
|
+
data
|
|
145
|
+
);
|
|
146
|
+
if (error) {
|
|
147
|
+
return error;
|
|
148
|
+
} else {
|
|
149
|
+
await this.adapter.update({ artifact });
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Each uploaded file must be attached to ADaaS adapter as soon as it is uploaded to ensure it is included in the ADaaS response message in case of a lambda timeout.
|
|
154
|
+
|
|
155
|
+
## Updating ADaaS snap-in state
|
|
156
|
+
|
|
157
|
+
ADaaS snap-ins keep their own state between sync runs, between the states of a particular sync run and between invocations within a particular state.
|
|
158
|
+
|
|
159
|
+
By managing its own state, the ADaaS snap-in keeps track of the process of extraction (what items have already been extracted and where to continue), the times of the last successful sync run and keeps record of progress of the extraction.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
async update({ artifacts, extractor_state}: AdapterUpdateParams)
|
|
163
|
+
```
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Artifact, EventType, ExtractorEventType } from '../types';
|
|
2
|
+
export declare function createFormData(preparedArtifact: any, fetchedObjects: object[] | object): FormData;
|
|
3
|
+
export declare function createArtifact(preparedArtifact: any, fetchedObjects: object[] | object, entity: string): Artifact;
|
|
4
|
+
export declare function getTimeoutExtractorEventType(eventType: EventType): ExtractorEventType | null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTimeoutExtractorEventType = exports.createArtifact = exports.createFormData = void 0;
|
|
4
|
+
const js_jsonl_1 = require("js-jsonl");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
function createFormData(
|
|
7
|
+
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
preparedArtifact, fetchedObjects) {
|
|
9
|
+
const formData = new FormData();
|
|
10
|
+
for (const item of preparedArtifact.form_data) {
|
|
11
|
+
formData.append(item.key, item.value);
|
|
12
|
+
}
|
|
13
|
+
const output = js_jsonl_1.jsonl.stringify(fetchedObjects);
|
|
14
|
+
formData.append('file', output);
|
|
15
|
+
return formData;
|
|
16
|
+
}
|
|
17
|
+
exports.createFormData = createFormData;
|
|
18
|
+
function createArtifact(
|
|
19
|
+
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
preparedArtifact, fetchedObjects, entity) {
|
|
21
|
+
const itemCount = Array.isArray(fetchedObjects) ? fetchedObjects.length : 1;
|
|
22
|
+
return {
|
|
23
|
+
item_count: itemCount,
|
|
24
|
+
id: preparedArtifact.id,
|
|
25
|
+
item_type: entity,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
exports.createArtifact = createArtifact;
|
|
29
|
+
function getTimeoutExtractorEventType(eventType) {
|
|
30
|
+
switch (eventType) {
|
|
31
|
+
case types_1.EventType.ExtractionMetadataStart:
|
|
32
|
+
return types_1.ExtractorEventType.ExtractionMetadataError;
|
|
33
|
+
case types_1.EventType.ExtractionDataStart:
|
|
34
|
+
case types_1.EventType.ExtractionDataContinue:
|
|
35
|
+
return types_1.ExtractorEventType.ExtractionDataProgress;
|
|
36
|
+
case types_1.EventType.ExtractionAttachmentsStart:
|
|
37
|
+
case types_1.EventType.ExtractionAttachmentsContinue:
|
|
38
|
+
return types_1.ExtractorEventType.ExtractionAttachmentsProgress;
|
|
39
|
+
case types_1.EventType.ExtractionExternalSyncUnitsStart:
|
|
40
|
+
return types_1.ExtractorEventType.ExtractionExternalSyncUnitsError;
|
|
41
|
+
default:
|
|
42
|
+
console.log('Event type not recognized in getTimeoutExtractorEventType function: ' +
|
|
43
|
+
eventType);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.getTimeoutExtractorEventType = getTimeoutExtractorEventType;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Artifact, AirdropEvent, ExtractorEventType, EventData } from '../types';
|
|
2
|
+
import { AdapterUpdateParams } from '../types/common';
|
|
3
|
+
/**
|
|
4
|
+
* Adapter class is used to interact with Airdrop platform. The class provides
|
|
5
|
+
* utilities to
|
|
6
|
+
* - emit control events to the platform
|
|
7
|
+
* - update the state of the extractor
|
|
8
|
+
* - add artifacts to the list of artifacts to be returned to the platform
|
|
9
|
+
* - Return the last saved state and artifacts in case timeout
|
|
10
|
+
* - The event sent in case of timeout for each event type is as follows:
|
|
11
|
+
* - EXTRACTION_EXTERNAL_SYNC_UNITS_START => EXTRACTION_EXTERNAL_SYNC_UNITS_ERROR
|
|
12
|
+
* - EXTRACTION_METADATA_START => EXTRACTION_METADATA_ERROR
|
|
13
|
+
* - EXTRACTION_DATA_START => EXTRACTION_DATA_PROGRESS
|
|
14
|
+
* - EXTRACTION_DATA_CONTINUE => EXTRACTION_DATA_PROGRESS
|
|
15
|
+
* - EXTRACTION_ATTACHMENTS_START => EXTRACTION_ATTACHMENTS_PROGRESS
|
|
16
|
+
* - EXTRACTION_ATTACHMENTS_CONTINUE => EXTRACTION_ATTACHMENTS_PROGRESS
|
|
17
|
+
*
|
|
18
|
+
* @class Adapter
|
|
19
|
+
* @constructor
|
|
20
|
+
* @param {AirdropEvent} event - The event object received from the platform
|
|
21
|
+
* @param {object=} state - Optional state object to be passed to the extractor. If not provided, an empty object is used.
|
|
22
|
+
*/
|
|
23
|
+
export declare class Adapter {
|
|
24
|
+
private artifacts;
|
|
25
|
+
/** Adapter level state to return to the platform */
|
|
26
|
+
private extractorState;
|
|
27
|
+
private event;
|
|
28
|
+
private callbackUrl;
|
|
29
|
+
private devrevToken;
|
|
30
|
+
constructor(event: AirdropEvent, state?: object);
|
|
31
|
+
/**
|
|
32
|
+
* Adds artifact to the list of artifacts.
|
|
33
|
+
* Overrides extractor state if provided.
|
|
34
|
+
*
|
|
35
|
+
* @param {AdapterUpdateParams} params - The parameters to update the adapter
|
|
36
|
+
* @param {Artifact=} params.artifact - The artifact to be added to the list of artifacts
|
|
37
|
+
* @param {object=} params.extractor_state - The state object to be updated
|
|
38
|
+
*/
|
|
39
|
+
update(params: AdapterUpdateParams): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Emits an event to the platform.
|
|
42
|
+
*
|
|
43
|
+
* @param {ExtractorEventType} newEventType - The event type to be emitted
|
|
44
|
+
* @param {EventData=} data - The data to be sent with the event
|
|
45
|
+
*/
|
|
46
|
+
emit(newEventType: ExtractorEventType, data?: EventData): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Returns the list of artifacts stored in the adapter.
|
|
49
|
+
*
|
|
50
|
+
* @return The list of artifacts
|
|
51
|
+
*/
|
|
52
|
+
getArtifacts(): Artifact[];
|
|
53
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Adapter = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const helpers_1 = require("../adapter/helpers");
|
|
9
|
+
/**
|
|
10
|
+
* Adapter class is used to interact with Airdrop platform. The class provides
|
|
11
|
+
* utilities to
|
|
12
|
+
* - emit control events to the platform
|
|
13
|
+
* - update the state of the extractor
|
|
14
|
+
* - add artifacts to the list of artifacts to be returned to the platform
|
|
15
|
+
* - Return the last saved state and artifacts in case timeout
|
|
16
|
+
* - The event sent in case of timeout for each event type is as follows:
|
|
17
|
+
* - EXTRACTION_EXTERNAL_SYNC_UNITS_START => EXTRACTION_EXTERNAL_SYNC_UNITS_ERROR
|
|
18
|
+
* - EXTRACTION_METADATA_START => EXTRACTION_METADATA_ERROR
|
|
19
|
+
* - EXTRACTION_DATA_START => EXTRACTION_DATA_PROGRESS
|
|
20
|
+
* - EXTRACTION_DATA_CONTINUE => EXTRACTION_DATA_PROGRESS
|
|
21
|
+
* - EXTRACTION_ATTACHMENTS_START => EXTRACTION_ATTACHMENTS_PROGRESS
|
|
22
|
+
* - EXTRACTION_ATTACHMENTS_CONTINUE => EXTRACTION_ATTACHMENTS_PROGRESS
|
|
23
|
+
*
|
|
24
|
+
* @class Adapter
|
|
25
|
+
* @constructor
|
|
26
|
+
* @param {AirdropEvent} event - The event object received from the platform
|
|
27
|
+
* @param {object=} state - Optional state object to be passed to the extractor. If not provided, an empty object is used.
|
|
28
|
+
*/
|
|
29
|
+
class Adapter {
|
|
30
|
+
constructor(event, state) {
|
|
31
|
+
this.event = event;
|
|
32
|
+
this.artifacts = [];
|
|
33
|
+
this.extractorState = state || {};
|
|
34
|
+
this.callbackUrl = event.payload.event_context.callback_url;
|
|
35
|
+
this.devrevToken = event.context.secrets["service_account_token"];
|
|
36
|
+
// Once lambda is near to timeout, Snap-in needs to submit the information about artifacts
|
|
37
|
+
// that have been uploaded and state, so next time it is run, can continue where it has left off
|
|
38
|
+
setTimeout(async () => {
|
|
39
|
+
const extractorEventType = (0, helpers_1.getTimeoutExtractorEventType)(this.event.payload.event_type);
|
|
40
|
+
if (extractorEventType) {
|
|
41
|
+
await this.emit(extractorEventType, { artifacts: this.artifacts });
|
|
42
|
+
}
|
|
43
|
+
}, 12 * 60 * 1000);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Adds artifact to the list of artifacts.
|
|
47
|
+
* Overrides extractor state if provided.
|
|
48
|
+
*
|
|
49
|
+
* @param {AdapterUpdateParams} params - The parameters to update the adapter
|
|
50
|
+
* @param {Artifact=} params.artifact - The artifact to be added to the list of artifacts
|
|
51
|
+
* @param {object=} params.extractor_state - The state object to be updated
|
|
52
|
+
*/
|
|
53
|
+
async update(params) {
|
|
54
|
+
if (params.artifact) {
|
|
55
|
+
this.artifacts.push(params.artifact);
|
|
56
|
+
}
|
|
57
|
+
if (params.extractor_state) {
|
|
58
|
+
this.extractorState = params.extractor_state;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Emits an event to the platform.
|
|
63
|
+
*
|
|
64
|
+
* @param {ExtractorEventType} newEventType - The event type to be emitted
|
|
65
|
+
* @param {EventData=} data - The data to be sent with the event
|
|
66
|
+
*/
|
|
67
|
+
async emit(newEventType, data) {
|
|
68
|
+
const newEvent = {
|
|
69
|
+
extractor_state: JSON.stringify(this.extractorState),
|
|
70
|
+
event_type: newEventType,
|
|
71
|
+
event_context: {
|
|
72
|
+
uuid: this.event.payload.event_context.uuid,
|
|
73
|
+
sync_run: this.event.payload.event_context.sync_run_id,
|
|
74
|
+
},
|
|
75
|
+
event_data: Object.assign(Object.assign({}, data), { artifacts: this.artifacts }),
|
|
76
|
+
};
|
|
77
|
+
// If sync_unit_id is present in the event, add it to the new event
|
|
78
|
+
if (this.event.payload.event_context.sync_unit_id) {
|
|
79
|
+
newEvent.event_context.sync_unit =
|
|
80
|
+
this.event.payload.event_context.sync_unit_id;
|
|
81
|
+
}
|
|
82
|
+
console.log('Event that will be emitted: ' + JSON.stringify(newEvent));
|
|
83
|
+
try {
|
|
84
|
+
await axios_1.default.post(this.callbackUrl, Object.assign({}, newEvent), {
|
|
85
|
+
headers: {
|
|
86
|
+
Accept: 'application/json, text/plain, */*',
|
|
87
|
+
Authorization: this.devrevToken,
|
|
88
|
+
'Content-Type': 'application/json',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
// If this request fails the extraction will be stuck in loop and
|
|
94
|
+
// we need to stop it through UI or think about retrying this request
|
|
95
|
+
console.log('Emitting failed for this event: ' +
|
|
96
|
+
JSON.stringify(newEvent) +
|
|
97
|
+
', error: ' +
|
|
98
|
+
error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns the list of artifacts stored in the adapter.
|
|
103
|
+
*
|
|
104
|
+
* @return The list of artifacts
|
|
105
|
+
*/
|
|
106
|
+
getArtifacts() {
|
|
107
|
+
return this.artifacts;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.Adapter = Adapter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const types_1 = require("../types");
|
|
4
|
+
const index_1 = require("./index");
|
|
5
|
+
jest.useFakeTimers();
|
|
6
|
+
const defaultEvent = {
|
|
7
|
+
context: {
|
|
8
|
+
secrets: {
|
|
9
|
+
service_account_token: 'mockToken',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
payload: {
|
|
13
|
+
connection_data: {
|
|
14
|
+
org_id: 'mockOrgId',
|
|
15
|
+
org_name: 'mockOrgName',
|
|
16
|
+
key: 'mockKey',
|
|
17
|
+
key_type: 'mockKeyType',
|
|
18
|
+
},
|
|
19
|
+
event_context: {
|
|
20
|
+
mode: 'INITIAL',
|
|
21
|
+
uuid: 'mockUuid',
|
|
22
|
+
callback_url: 'mockCallbackUrl',
|
|
23
|
+
dev_org_id: 'DEV-TESTORG',
|
|
24
|
+
dev_user_id: 'DEV-TESTUSER',
|
|
25
|
+
external_system_id: 'TESTSYSTEM',
|
|
26
|
+
sync_run_id: 'mockSyncRunId',
|
|
27
|
+
},
|
|
28
|
+
event_type: types_1.EventType.ExtractionExternalSyncUnitsStart,
|
|
29
|
+
},
|
|
30
|
+
execution_metadata: {
|
|
31
|
+
devrev_endpoint: 'http://api.dev.devrev-eng.ai',
|
|
32
|
+
},
|
|
33
|
+
input_data: {
|
|
34
|
+
global_values: {},
|
|
35
|
+
event_sources: {},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
describe('Adapter', () => {
|
|
39
|
+
let adapter;
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
// Initialize the Adapter instance with mock data
|
|
42
|
+
adapter = new index_1.Adapter(defaultEvent);
|
|
43
|
+
});
|
|
44
|
+
afterEach(async () => {
|
|
45
|
+
// Clear all timers
|
|
46
|
+
jest.clearAllTimers();
|
|
47
|
+
// Clear all instances
|
|
48
|
+
jest.clearAllMocks();
|
|
49
|
+
});
|
|
50
|
+
it('should add artifact to the list of artifacts', async () => {
|
|
51
|
+
const artifact = {
|
|
52
|
+
id: 'mockId',
|
|
53
|
+
item_type: 'mockItemType',
|
|
54
|
+
item_count: 1,
|
|
55
|
+
// Mock artifact data
|
|
56
|
+
};
|
|
57
|
+
adapter.update({ artifact: artifact });
|
|
58
|
+
const artifacts = adapter.getArtifacts();
|
|
59
|
+
expect(artifacts).toContain(artifact);
|
|
60
|
+
// Run all timers
|
|
61
|
+
jest.runAllTimers();
|
|
62
|
+
});
|
|
63
|
+
it('should set extractor state if provided', async () => {
|
|
64
|
+
const state = {
|
|
65
|
+
testing: false,
|
|
66
|
+
};
|
|
67
|
+
adapter = new index_1.Adapter(Object.assign({}, defaultEvent), state);
|
|
68
|
+
expect(adapter['extractorState']).toEqual(state);
|
|
69
|
+
const newState = {
|
|
70
|
+
testing: true,
|
|
71
|
+
};
|
|
72
|
+
adapter.update({ extractor_state: newState });
|
|
73
|
+
expect(adapter['extractorState']).toEqual(newState);
|
|
74
|
+
// Run all timers
|
|
75
|
+
jest.runAllTimers();
|
|
76
|
+
});
|
|
77
|
+
it('update both an artifact and extractor state', async () => {
|
|
78
|
+
const artifact = {
|
|
79
|
+
id: 'mockId',
|
|
80
|
+
item_type: 'mockItemType',
|
|
81
|
+
item_count: 1,
|
|
82
|
+
};
|
|
83
|
+
const state = {
|
|
84
|
+
willUpdateToTrue: false,
|
|
85
|
+
willAdd4: 0,
|
|
86
|
+
};
|
|
87
|
+
adapter.update({ artifact: artifact, extractor_state: state });
|
|
88
|
+
const artifacts = adapter.getArtifacts();
|
|
89
|
+
expect(artifacts).toContain(artifact);
|
|
90
|
+
expect(adapter['extractorState']).toEqual(state);
|
|
91
|
+
const newState = {
|
|
92
|
+
willUpdateToTrue: true,
|
|
93
|
+
willAdd4: 4,
|
|
94
|
+
newField: 'newField',
|
|
95
|
+
};
|
|
96
|
+
const newArtifact = {
|
|
97
|
+
id: 'newMockId',
|
|
98
|
+
item_type: 'newMockItemType',
|
|
99
|
+
item_count: 2,
|
|
100
|
+
};
|
|
101
|
+
adapter.update({ artifact: newArtifact, extractor_state: newState });
|
|
102
|
+
// Run all timers
|
|
103
|
+
jest.runAllTimers();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DemoExtractor = void 0;
|
|
7
|
+
const types_1 = require("../types");
|
|
8
|
+
const adapter_1 = require("../adapter");
|
|
9
|
+
const uploader_1 = require("../uploader");
|
|
10
|
+
const recipe_json_1 = __importDefault(require("./recipe.json"));
|
|
11
|
+
class DemoExtractor {
|
|
12
|
+
async run(event) {
|
|
13
|
+
console.log('Event in DemoExtractor run function: ' + JSON.stringify(event));
|
|
14
|
+
const adapter = new adapter_1.Adapter(event);
|
|
15
|
+
const uploader = new uploader_1.Uploader(event.execution_metadata.devrev_endpoint, event.context.secrets["service_account_token"]);
|
|
16
|
+
switch (event.payload.event_type) {
|
|
17
|
+
case types_1.EventType.ExtractionExternalSyncUnitsStart: {
|
|
18
|
+
const externalSyncUnits = [
|
|
19
|
+
{
|
|
20
|
+
id: 'devrev',
|
|
21
|
+
name: 'devrev',
|
|
22
|
+
description: 'Loopback for DevRev',
|
|
23
|
+
item_count: 0,
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionExternalSyncUnitsDone, {
|
|
27
|
+
external_sync_units: externalSyncUnits,
|
|
28
|
+
});
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case types_1.EventType.ExtractionMetadataStart: {
|
|
32
|
+
const metadata = [
|
|
33
|
+
{
|
|
34
|
+
item: 'contacts',
|
|
35
|
+
fields: ['name', 'lastName'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
item: 'users',
|
|
39
|
+
fields: ['name', 'lastName'],
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
const { artifact, error } = await uploader.upload('loopback_metadata_1.jsonl', 'metadata', metadata);
|
|
43
|
+
if (error) {
|
|
44
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionMetadataError, {
|
|
45
|
+
error,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
await adapter.update({ artifact });
|
|
50
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionMetadataDone);
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case types_1.EventType.ExtractionDataStart: {
|
|
55
|
+
const contacts = [
|
|
56
|
+
{
|
|
57
|
+
name: 'John',
|
|
58
|
+
lastName: 'Doe',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'Jane',
|
|
62
|
+
lastName: 'Doe',
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
const { artifact, error } = await uploader.upload('loopback_contacts_1.json', 'contacts', contacts);
|
|
66
|
+
if (error) {
|
|
67
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionDataError, {
|
|
68
|
+
error,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
await adapter.update({ artifact });
|
|
73
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionDataProgress, {
|
|
74
|
+
progress: 50,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case types_1.EventType.ExtractionDataContinue: {
|
|
80
|
+
const users = [
|
|
81
|
+
{
|
|
82
|
+
name: 'John',
|
|
83
|
+
lastName: 'Phd',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'Jane',
|
|
87
|
+
lastName: 'Phd',
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
const { artifact, error } = await uploader.upload('loopback_users_1.json', 'users', users);
|
|
91
|
+
if (error) {
|
|
92
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionDataError, {
|
|
93
|
+
error,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
await adapter.update({ artifact });
|
|
98
|
+
// TODO: Add separated function for uploading recipe.json?
|
|
99
|
+
const { artifact: recipe, error } = await uploader.upload('recipe.json', 'initial_domain_mapping', recipe_json_1.default);
|
|
100
|
+
await adapter.update({ artifact: recipe });
|
|
101
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionDataDone, {
|
|
102
|
+
progress: 100,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case types_1.EventType.ExtractionDataDelete: {
|
|
108
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionDataDeleteDone);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case types_1.EventType.ExtractionAttachmentsStart: {
|
|
112
|
+
const attachment1 = ['This is attachment1.txt content'];
|
|
113
|
+
const { artifact, error } = await uploader.upload('attachment1.txt', 'attachment', attachment1);
|
|
114
|
+
if (error) {
|
|
115
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionAttachmentsError, {
|
|
116
|
+
error,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
await adapter.update({ artifact });
|
|
121
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionAttachmentsProgress);
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case types_1.EventType.ExtractionAttachmentsContinue: {
|
|
126
|
+
const attachment2 = ['This is attachment2.txt content'];
|
|
127
|
+
const { artifact, error } = await uploader.upload('attachment2.txt', 'attachment', attachment2);
|
|
128
|
+
if (error) {
|
|
129
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionAttachmentsError, {
|
|
130
|
+
error,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
await adapter.update({ artifact });
|
|
135
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionAttachmentsDone);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case types_1.EventType.ExtractionAttachmentsDelete: {
|
|
140
|
+
await adapter.emit(types_1.ExtractorEventType.ExtractionAttachmentsDeleteDone);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
default: {
|
|
144
|
+
console.log('Event in DemoExtractor run not recognized: ' + JSON.stringify(event));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
exports.DemoExtractor = DemoExtractor;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"initial_object_mappings": [
|
|
3
|
+
{
|
|
4
|
+
"external_type": "contacts",
|
|
5
|
+
"possible_targets": {
|
|
6
|
+
"rev_user": {}
|
|
7
|
+
},
|
|
8
|
+
"default_target": "rev_user",
|
|
9
|
+
"allow_item_type_decisions": true
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"external_type": "users",
|
|
13
|
+
"possible_targets": {
|
|
14
|
+
"dev_user": {}
|
|
15
|
+
},
|
|
16
|
+
"default_target": "dev_user",
|
|
17
|
+
"allow_item_type_decisions": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"external_type": "tickets",
|
|
21
|
+
"possible_targets": {
|
|
22
|
+
"work.ticket": {}
|
|
23
|
+
},
|
|
24
|
+
"default_target": "work.ticket",
|
|
25
|
+
"allow_item_type_decisions": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"external_type": "conversations",
|
|
29
|
+
"possible_targets": {
|
|
30
|
+
"comment": {}
|
|
31
|
+
},
|
|
32
|
+
"default_target": "comment",
|
|
33
|
+
"allow_item_type_decisions": true
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"external_system_short_name": "source-system"
|
|
37
|
+
}
|