Skip to content

Commit 3e440b8

Browse files
tjazerzendusanopatricijabrecko
authored
Updated shared files (#16)
This PR includes a couple of changes regarding the shared files for code generation with Codeplain (updates to docs, templates, updates to template Plain file, etc.). Related issue: https://app.devrev.ai/devrev/works/ISS-201997 --------- Co-authored-by: Dusan Omercevic <dusan.omercevic@gmail.com> Co-authored-by: Patricija Brečko <patricija.brecko@devrev.ai>
1 parent d77ad0c commit 3e440b8

19 files changed

+402
-158
lines changed

base_folder/src/core/types.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Type definitions for DevRev function inputs and related types
3+
*/
4+
5+
export type Context = {
6+
// ID of the dev org for which the function is being invoked.
7+
dev_oid: string;
8+
// ID of the automation/command/snap-kit Action/Event Source for which the function is being invoked.
9+
source_id: string;
10+
// ID of the snap-in as part of which the function is being invoked.
11+
snap_in_id: string;
12+
// ID of the snap-in Version as part of which the function is being invoked.
13+
snap_in_version_id: string;
14+
// ID of the service account.
15+
service_account_id: string;
16+
// This secrets map would contain some secrets which platform would provide to the snap-in.
17+
// `service_account_token`: This is the token of the service account which belongs to this snap-in. This can be used to make API calls to DevRev.
18+
// `actor_session_token`: For commands, and snap-kits, where the user is performing some action, this is the token of the user who is performing the action.
19+
secrets: Record<string, string>;
20+
};
21+
22+
export type ExecutionMetadata = {
23+
// A unique id for the function invocation. Can be used to filter logs for a particular invocation.
24+
request_id: string;
25+
// Function name as defined in the manifest being invoked.
26+
function_name: string;
27+
// Type of event that triggered the function invocation as defined in manifest.
28+
event_type: string;
29+
// DevRev endpoint to which the function can make API calls.
30+
// Example : "https://api.devrev.ai/"
31+
devrev_endpoint: string;
32+
};
33+
34+
export type InputData = {
35+
// Map of organization inputs and their corresponding values stored in snap-in.
36+
// The values are passed as string and typing need to be handled by the function
37+
global_values: Record<string, string>;
38+
// Map of event sources and their corresponding ids stored in snap-in.
39+
// These could be used to schedule events on a schedule based event source.
40+
event_sources: Record<string, string>;
41+
};
42+
43+
// Event sent to our app.
44+
export type FunctionInput = {
45+
// Actual payload of the event.
46+
payload: Record<string, any>;
47+
// Context of the function invocation.
48+
context: Context;
49+
// Metadata of the function invocation.
50+
execution_metadata: ExecutionMetadata;
51+
input_data: InputData;
52+
};

base_folder/src/core/utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { AirdropEvent, AirdropMessage } from '@devrev/ts-adaas';
2+
import { FunctionInput } from './types';
3+
4+
export function convertToAirdropEvent(fi: FunctionInput): AirdropEvent {
5+
// Create a properly structured AirdropMessage
6+
const airdropMessage: AirdropMessage = {
7+
connection_data: fi.payload.connection_data,
8+
event_context: {
9+
...fi.payload.event_context,
10+
...fi.context,
11+
request_id: fi.execution_metadata?.request_id,
12+
},
13+
event_type: fi.payload.event_type,
14+
event_data: fi.payload.event_data || {},
15+
};
16+
17+
return {
18+
context: {
19+
...fi.context,
20+
secrets: {
21+
service_account_token: fi.context.secrets?.service_account_token || '',
22+
...fi.context.secrets,
23+
},
24+
},
25+
payload: airdropMessage,
26+
execution_metadata: fi.execution_metadata,
27+
input_data: fi.input_data,
28+
};
29+
}
30+

devrev-snapin-template.plain

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- The Function implements part of The Snap-In functionality.
66
- Details on how The Function must be invoked are provided in the resource [Function Invocation](docs/function_invocation.mdx).
7+
- The Function should only take the first event from the array of events and process it. The Function can be invoked multiple times for multiple events.
78

89
- The Airdrop SDK Library for TypeScript helps developers build snap-ins that integrate with DevRev’s Airdrop platform.
910
- The documentation for the The Airdrop SDK Library is provided in the resource [airdrop_sdk_library_documentation.md](docs/airdrop_sdk_library_documentation.md).
@@ -13,6 +14,12 @@
1314

1415
- The Initial Domain Mapping is a JSON object that defines how the logical domain model described by The External Domain Metadata maps to the target domain model in DevRev. It specifies the correspondence between each external record type and DevRev leaf type, as well as the mapping of individual fields, including transformation logic, value conversions, and any fixed or default values.
1516

17+
- The External Sync Unit Item Count is a numeric field representing the total number of items (such as cards) contained within a given external sync unit. It is accessible as "item_count" field of the external sync unit.
18+
19+
- The Normalization Function is a function that is responsible for normalizing the data from The API to the data expected by The External Domain Metadata JSON object's record type.
20+
21+
- The Extraction Function is The Function named "extraction". It is responsible for pushing data to the DevRev servers (The DevRev Servers) based on various event types.
22+
- "event_type" equals "EXTRACTION_DATA_START" or "event_type" equals "EXTRACTION_DATA_CONTINUE" and we get a timeout, The Worker Thread should emit event "EXTRACTION_DATA_PROGRESS".
1623

1724
***Non-Functional Requirements:***
1825

@@ -21,8 +28,6 @@
2128
- The Snap-In will run on Node.js as the runtime environment.
2229

2330
- The Snap-In should use The Airdrop SDK Library version "1.5.1" for handling data extraction and loading, pushing data, event-driven actions, state management, and artifact handling.
24-
- Please note that 'processTask' function **only** executes task if 'processTask' function is called from a worker thread. Therefore 'processTask' function should be called from a worker thread spawned using 'Spawn' class.
25-
- If the worker is written in TypeScript, it must be referenced with a .ts extension, not .js.
2631

2732
- Use snake_case for JSON keys.
2833

@@ -32,16 +37,18 @@
3237

3338
- The shared code (including libraries, utilities, adapters, and other common code) used by multiple The Function should be placed in the `src/core` folder. The code in this folder should not depend on any specific The Function implementation.
3439

40+
- During the data extraction phase, The Normalization Function's "data" field for specific record type should map precisely the fields that are present in The External Domain Metadata JSON object for that record type. The Normalization Function should map no additional fields other than those specified in The External Domain Metadata JSON object, and also no object fields from The External Domain Metadata JSON should be omitted in normalization.
41+
3542

3643
***Test Requirements:***
3744

38-
- The Conformance Tests of The Snap-In should be implemented in TypeScript.
45+
- The Conformance Tests of The Snap-In should be implemented in TypeScript with target language version es2017. The Conformance Tests should utilize the TypeScript target language version es2017 with configuration settings for module interoperability, strict type checking, declaration file generation, and JSON module support.
3946

4047
- The Conformance Tests must be in the top folder.
4148

4249
- The Conformance Tests will run on Node.js as the runtime environment.
4350

44-
- The Conformance Tests will be executed using the "npm test" command.
51+
- The Conformance Tests will be executed using the "npm test" command. The command "npm test" should be accessible in the `package.json` file of The Conformance Tests.
4552

4653
- The Conformance Tests must complete within 120 seconds (The Conformance Tests Timeout). If The Conformance Tests Timeout elapses, the process executing The Conformance Tests will be terminated.
4754

@@ -55,28 +62,36 @@
5562

5663
- The Worker Data Server is part of The DevRev Server and is accessible at http://localhost:8003/external-worker, so it must not be mocked.
5764

65+
- The `jest.setup.js` file is managed externally and cannot be modified within The Conformance Tests.
5866

59-
# The TypeScript Node.js application boilerplate
67+
- All test files from The Conformance Tests must use the `.test.ts` suffix to be discoverable by Jest's default test pattern matching.
6068

61-
***Functional Requirements:***
69+
- The Conformance Tests should send a single event to the Snap-In Server in the request body without wrapping it in an array.
6270

63-
- Implement The Function that only checks if The Function can be invoked.
6471

65-
- Implement The Function that only checks if the data extraction workflow can be invoked.
72+
# The TypeScript Node.js application boilerplate
6673

67-
***Acceptance Tests:***
74+
***Non-Functional Requirements:***
75+
76+
- Logic of pushing data to The DevRev Servers must be implemented in the worker thread and invoked with the "spawn" method from The Airdrop SDK Library.
6877

69-
- Test the data extraction workflow invocation using the resource [extraction_health_check.json](test_data/extraction_health_check.json).
78+
- The Function being implemented *MUST not* be called "extraction" or "loading".
7079

71-
- Implement The Function that checks if pushing data works. The URL where data is to be pushed is provided in the "callback_url" field of the "event_context" dictionary which is part of "payload" dictionary.
80+
***Functional Requirements:***
81+
82+
- Implement The Function that only checks if The Function can be invoked.
7283

73-
- Implement The Function that provides a test of the 'external sync units' part of the extraction workflow. The name of The Function must be 'extraction_external_sync_unit_check'.
84+
- Implement The Function called "test_external_sync_units" that provides a test of the 'external sync units' part of the extraction workflow as described in the resource [external-sync-units-extraction.mdx](docs/external-sync-units-extraction.mdx). Only check if the extraction can be triggered and that it successfully completes. Disregard all other details.
7485

7586
***Acceptance Tests:***
7687

7788
- Test The Function using the resource [external_sync_unit_check.json](test_data/external_sync_unit_check.json). Test is successful if The Callback Server receives from DevRev an event with "event_type" that equals "EXTRACTION_EXTERNAL_SYNC_UNITS_DONE". The test must not send the event directly to The Callback Server.
7889

79-
- Implement The Function that provides a test of the 'data extraction' part of the extraction workflow as described in the resource [data-extraction.md](docs/data-extraction.md). Only check if data extraction can be triggered and that it successfully completes. Disregard all other details. The name of The Function must be 'data_extraction_check'.
90+
- Implement The Function called "data_extraction_check" (The Data Extraction Check Function) that provides a test of the 'data extraction' part of the extraction workflow as described in the resource [data-extraction.md](docs/data-extraction.md). The Data Extraction Check Function should:
91+
- Initialize a single repo "users"
92+
- normalize the users data using The Normalization Function
93+
- push the normalized users data to The DevRev Servers
94+
- Note: The Data Extraction Check Function should be simplified and should disregard all other details.
8095

8196
***Acceptance Tests:***
8297

docs/airdrop_sdk_library_documentation.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,8 @@ Defines the structure of events sent to external extractors from Airdrop platfor
363363
Required. An object containing:
364364

365365
- _secrets_: An object containing:
366-
- _service_account_token_: Required. A **string** representing the DevRev authentication token for Airdrop platform
367-
- _snap_in_version_id_: Required. A **string** representing the version ID of the snap-in
368-
- _snap_in_id_: Required. A **string** representing the snap-in ID.
366+
- _service_account_token_: A **string** representing the DevRev authentication token for Airdrop platform
367+
- _snap_in_version_id_: A **string** representing the version ID of the snap-in
369368

370369
- _payload_
371370

@@ -426,7 +425,7 @@ This function initializes a new worker thread and oversees its lifecycle. It sho
426425
#### Usage
427426

428427
```typescript
429-
spawn({ event, initialState, workerPath, options });
428+
spawn({ event, initialState, workerPath, options, initialDomainMapping });
430429
```
431430

432431
#### Parameters
@@ -443,6 +442,10 @@ spawn({ event, initialState, workerPath, options });
443442

444443
Required. A **string** that represents the path to the worker file.
445444

445+
- _initialDomainMapping_
446+
447+
Optional. An **object** containing the initial domain mapping.
448+
446449
- _options_
447450

448451
Optional. An object of type **WorkerAdapterOptions**, which will be passed to the newly created worker. This worker will then initialize a `WorkerAdapter` by invoking the `processTask` function. The options include:
@@ -458,10 +461,6 @@ spawn({ event, initialState, workerPath, options });
458461
- `batchSize`
459462

460463
A **number** that determines the maximum number of items to be processed and saved to an intermediary file before being sent to the Airdrop platform. The default batch size is 2,000.
461-
462-
- `initialDomainMapping`
463-
464-
Optional. A **string** containing the initial domain mapping.
465464

466465
#### Return value
467466

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Here are the rules when `EXTRACTION_DATA_START` or `EXTRACTION_DATA_CONTINUE` event is received by the snap-in:
2+
3+
- If there's a timeout ("onTimeout" method in "spawn" method), the snap-in should emit an event with type `EXTRACTION_DATA_PROGRESS` (no "data" payload).
4+
- If the extraction is rate-limited by the external system, the snap-in should emit an event with type `EXTRACTION_DATA_DELAY` ("data" payload should contain attribute "delay" - delay in seconds as an integer).
5+
- If the extraction is successful and all the resources have been extracted, the snap-in should emit an event with type `EXTRACTION_DATA_DONE` (no "data" payload).
6+
- In case of unexpected error, the snap-in should emit an event with type `EXTRACTION_DATA_ERROR` ("data" payload should contain attribute "error" - error message as a string).
7+
8+
NOTE: In all cases, only a single event should be emitted.

docs/data-extraction.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ Behind the scenes, the SDK library stores items pushed to the repository and upl
112112
### Data normalization
113113

114114
Extracted data must be normalized to fit the domain metadata defined in the `external-domain-metadata.json` file.
115-
More details on this process are provided in the [Metadata extraction](/public/snapin-development/adaas/metadata-extraction) section.
116115

117116
Normalization rules:
118117

@@ -124,6 +123,7 @@ Normalization rules:
124123
- References: references must be strings, not numbers or objects.
125124
- Number fields must be valid JSON numbers (not strings).
126125
- Multiselect fields must be provided as an array (not CSV).
126+
- Rich text: Strings from 3rd party services should be converted to the rich text by splitting the string by newlines, filtering out empty lines, and returning an array of strings.
127127

128128
Extracted items are automatically normalized when pushed to the `repo` if a normalization function is provided under the `normalize` key in the repo object.
129129

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
An _external sync unit_ refers to a single unit in the external system that is being airdropped to DevRev.
2+
In some systems, this is a project; in some it is a repository; in support systems it could be
3+
called a brand or an organization.
4+
What a unit of data is called and what it represents depends on the external system's domain model.
5+
It usually combines contacts, users, work-like items, and comments into a unit of domain objects.
6+
7+
In the external sync unit extraction phase, the snap-in is expected to obtain a list of external
8+
sync units that it can extract from the external system API and send it to Airdrop in its response.
9+
10+
External sync unit extraction is executed only during the initial import.
11+
12+
## Triggering event
13+
14+
Airdrop starts the external sync unit extraction by sending a message with the event type `EXTRACTION_EXTERNAL_SYNC_UNITS_START`.
15+
16+
The snap-in must reply to Airdrop with an `EXTRACTION_EXTERNAL_SYNC_UNITS_DONE` message when finished,
17+
or `EXTRACTION_EXTERNAL_SYNC_UNITS_ERROR` if an error occurs.
18+
19+
## Implementation
20+
21+
22+
The snap-in should emit the list of external sync units in the given format:
23+
24+
```typescript
25+
const externalSyncUnits: ExternalSyncUnit[] = [
26+
{
27+
id: "devrev",
28+
name: "devrev",
29+
description: "Demo external sync unit",
30+
item_count: 100,
31+
},
32+
];
33+
```
34+
35+
- `id`: The unique identifier in the external system.
36+
- `name`: The human-readable name in the external system.
37+
- `description`: The short description if the external system provides it.
38+
- `item_count`: The number of items (issues, tickets, comments or others) in the external system.
39+
Item count should be provided if it can be obtained in a lightweight manner, such as by calling an API endpoint.
40+
If there is no such way to get it (for example, if the items would need to be extracted to count them),
41+
then the item count should be `-1` to avoid blocking the import with long-running queries.
42+
43+
The snap-in must respond to Airdrop with a message, which contains a list of external sync units as a payload:
44+
45+
```typescript
46+
await adapter.emit(ExtractorEventType.ExtractionExternalSyncUnitsDone, {
47+
external_sync_units: externalSyncUnits,
48+
});
49+
```
50+
51+
or an error:
52+
53+
```typescript
54+
await adapter.emit(ExtractorEventType.ExtractionExternalSyncUnitsError, {
55+
error: {
56+
message: "Failed to extract external sync units. Lambda timeout.",
57+
},
58+
});
59+
```
60+
61+
**The snap-in must always emit a single message.**
62+
63+
To test your changes, start a new airdrop in the DevRev App. If external sync units extraction is successful, you should be prompted to choose an external sync unit from the list.

docs/function_invocation.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
The function is invoked by the runner.ts file.
2-
3-
runner.ts processes events of type "any", which are basically events of type "AirdropEvent" with some additional fields, and passes the events to the function. You can ignore the additional fields.
1+
A function can be invoked synchronously or asynchronously.
42

53
You need to implement the run method in your function. The run method is called when the function is invoked. The run method signature is defined below:
4+
65
```typescript
7-
async function run(events: []AirdropEvent): void;
6+
async function run(events: []FunctionInput): any;
87
```
9-
As of now, it's safe to assume that only one event is passed to the function at a time. The function can be invoked multiple times for multiple events.
8+
9+
The value returned from the `run` method is passed back in synchronous execution modes, such as commands, snap kit actions, and event source synchronous execution. In asynchronous execution modes, such as automation execution, the return value is ignored.

docs/incremental_mode.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
### Implementing Incremental Data Sync
2+
3+
Incremental data synchronization retrieves only records that have been created or updated since the last successful sync. This process requires persistently tracking the timestamp of the last successful data extraction. On subsequent runs, this timestamp is used to query the source system for changes.
4+
5+
Incremental mode should only be handled if the "event_type" is `EXTRACTION_DATA_START`.
6+
7+
To check if we're in incremental mode, you should check if the value of `adapter.event.payload.event_context.mode` is `SyncMode.INCREMENTAL`.
8+
9+
#### How implement incremental mode
10+
11+
If we're in incremental mode, you should reset The Extraction State, indicating the sync hasn't been completed yet for all data types that we support incremental mode.
12+
13+
Value of `adapter.state.lastSuccessfulSyncStarted` (of format ISO 8601 Extended Format with timezone) represents you the information since when you should query resources from the 3rd party API.
14+
15+
To retrieve only the resources from the API that have to be updated, filtering on The API should be implemented.
16+
17+
Note:
18+
- `adapter.state.lastSuccessfulSyncStarted` and `adapter.state.lastSyncStarted` are internal properties of the ts-adaas library, so no need to define it. This should be a read-only property.
19+
20+
#### Remember the last successful sync time
21+
22+
If the sync is successful, update the state object with the current time.
23+
24+
Note: No need to modify any time-related properties in adapter.state object.

0 commit comments

Comments
 (0)