Skip to content

Commit 6d30a39

Browse files
Support for sync from devrev to external system (#12)
Co-authored-by: Samo Dekleva <[email protected]>
1 parent b58e106 commit 6d30a39

22 files changed

+1182
-65
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Release Notes
44

5+
#### v1.1.0
6+
7+
- Support for sync from DevRev to external system. Known limitations: no support for loading attachments.
8+
59
#### v1.0.4
610

711
- Fix logging from worker threads.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devrev/ts-adaas",
3-
"version": "1.0.4",
3+
"version": "1.1.0",
44
"description": "Typescript library containing the ADaaS(AirDrop as a Service) control protocol.",
55
"type": "commonjs",
66
"main": "./dist/index.js",

src/common/constants.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ export const STATELESS_EVENT_TYPES = [
55
EventType.ExtractionMetadataStart,
66
EventType.ExtractionDataDelete,
77
EventType.ExtractionAttachmentsDelete,
8+
EventType.StartDeletingLoaderState,
9+
EventType.StartDeletingLoaderAttachmentsState,
810
];
911

10-
export const ALLOWED_EVENT_TYPES = [
12+
export const ALLOWED_EXTRACTION_EVENT_TYPES = [
1113
EventType.ExtractionExternalSyncUnitsStart,
1214
EventType.ExtractionMetadataStart,
1315
EventType.ExtractionDataStart,
@@ -18,6 +20,18 @@ export const ALLOWED_EVENT_TYPES = [
1820
EventType.ExtractionAttachmentsDelete,
1921
];
2022

23+
export const ALLOWED_LOADING_EVENT_TYPES = [
24+
EventType.StartLoadingData,
25+
EventType.ContinueLoadingData,
26+
EventType.StartDeletingLoaderState,
27+
EventType.StartDeletingLoaderAttachmentsState,
28+
];
29+
30+
export const ALLOWED_EVENT_TYPES = [
31+
...ALLOWED_EXTRACTION_EVENT_TYPES,
32+
...ALLOWED_LOADING_EVENT_TYPES,
33+
];
34+
2135
export const ARTIFACT_BATCH_SIZE = 2000;
2236
export const MAX_DEVREV_ARTIFACT_SIZE = 536870912; // 512MB
2337

src/common/control-protocol.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import {
55
EventData,
66
ExtractorEvent,
77
ExtractorEventType,
8+
LoaderEvent,
89
} from '../types/extraction';
10+
import { LoaderEventType } from '../types/loading';
911
import { formatAxiosError } from '../logger/logger';
1012

1113
export interface EmitInterface {
1214
event: AirdropEvent;
13-
eventType: ExtractorEventType;
15+
eventType: ExtractorEventType | LoaderEventType;
1416
data?: EventData;
1517
}
1618

@@ -19,7 +21,7 @@ export const emit = async ({
1921
eventType,
2022
data,
2123
}: EmitInterface): Promise<void | Error> => {
22-
const newEvent: ExtractorEvent = {
24+
const newEvent: ExtractorEvent | LoaderEvent = {
2325
event_type: eventType,
2426
event_context: {
2527
uuid: event.payload.event_context.uuid,

src/common/helpers.ts

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1-
import { EventType, ExtractorEventType } from '../types/extraction';
1+
import {
2+
AirdropEvent,
3+
EventType,
4+
ExtractorEventType,
5+
} from '../types/extraction';
6+
import {
7+
ActionType,
8+
FileToLoad,
9+
ItemTypeToLoad,
10+
LoaderEventType,
11+
LoaderReport,
12+
StatsFileObject,
13+
} from '../types/loading';
214

3-
export function getErrorExtractorEventType(eventType: EventType): {
4-
eventType: ExtractorEventType;
15+
export function getTimeoutErrorEventType(eventType: EventType): {
16+
eventType: ExtractorEventType | LoaderEventType;
517
} | null {
618
switch (eventType) {
719
case EventType.ExtractionMetadataStart:
@@ -30,11 +42,104 @@ export function getErrorExtractorEventType(eventType: EventType): {
3042
return {
3143
eventType: ExtractorEventType.ExtractionExternalSyncUnitsError,
3244
};
45+
46+
case EventType.StartLoadingData:
47+
case EventType.ContinueLoadingData:
48+
return {
49+
eventType: LoaderEventType.DataLoadingError,
50+
};
51+
case EventType.StartDeletingLoaderState:
52+
case EventType.StartDeletingLoaderAttachmentsState:
53+
return {
54+
eventType: LoaderEventType.LoaderStateDeletionError,
55+
};
3356
default:
3457
console.error(
35-
'Event type not recognized in getTimeoutExtractorEventType function: ' +
58+
'Event type not recognized in getTimeoutErrorEventType function: ' +
3659
eventType
3760
);
3861
return null;
3962
}
4063
}
64+
65+
export function getSyncDirection({ event }: { event: AirdropEvent }) {
66+
return event.payload.event_context.mode;
67+
}
68+
69+
export function getFilesToLoad({
70+
itemTypesToLoad,
71+
statsFile,
72+
}: {
73+
itemTypesToLoad: ItemTypeToLoad[];
74+
statsFile: StatsFileObject[];
75+
}): FileToLoad[] {
76+
const filesToLoad = [];
77+
78+
if (itemTypesToLoad.length === 0 || statsFile.length === 0) {
79+
return [];
80+
}
81+
82+
const supportedItemTypes = itemTypesToLoad.map(
83+
(itemTypeToLoad) => itemTypeToLoad.itemType
84+
);
85+
86+
const filteredStatsFile = statsFile.filter((file) =>
87+
supportedItemTypes.includes(file.item_type)
88+
);
89+
90+
const orderedFiles = filteredStatsFile.sort((a, b) => {
91+
const aIndex = supportedItemTypes.indexOf(a.item_type);
92+
const bIndex = supportedItemTypes.indexOf(b.item_type);
93+
94+
return aIndex - bIndex;
95+
});
96+
97+
for (const file of orderedFiles) {
98+
filesToLoad.push({
99+
id: file.id,
100+
file_name: file.file_name,
101+
itemType: file.item_type,
102+
count: parseInt(file.count),
103+
completed: false,
104+
lineToProcess: 0,
105+
});
106+
}
107+
108+
return filesToLoad;
109+
}
110+
111+
export function addReportToLoaderReport({
112+
loaderReports,
113+
report,
114+
}: {
115+
loaderReports: LoaderReport[];
116+
report: LoaderReport;
117+
}): LoaderReport[] {
118+
const existingReport = loaderReports.find(
119+
(loaderReport) => loaderReport.item_type === report.item_type
120+
);
121+
122+
if (existingReport) {
123+
existingReport[ActionType.CREATED] = existingReport[ActionType.CREATED]
124+
? report[ActionType.CREATED]
125+
? existingReport[ActionType.CREATED] + report[ActionType.CREATED]
126+
: existingReport[ActionType.CREATED]
127+
: report[ActionType.CREATED];
128+
129+
existingReport[ActionType.UPDATED] = existingReport[ActionType.UPDATED]
130+
? report[ActionType.UPDATED]
131+
? existingReport[ActionType.UPDATED] + report[ActionType.UPDATED]
132+
: existingReport[ActionType.UPDATED]
133+
: report[ActionType.UPDATED];
134+
135+
existingReport[ActionType.FAILED] = existingReport[ActionType.FAILED]
136+
? report[ActionType.FAILED]
137+
? existingReport[ActionType.FAILED] + report[ActionType.FAILED]
138+
: existingReport[ActionType.FAILED]
139+
: report[ActionType.FAILED];
140+
} else {
141+
loaderReports.push(report);
142+
}
143+
144+
return loaderReports;
145+
}

src/logger/logger.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,8 @@ it('formatAxiosError should return formatted error', () => {
5050
status: 500,
5151
data: 'Internal server error',
5252
method: 'GET',
53+
baseURL: undefined,
54+
url: undefined,
55+
payload: undefined,
5356
});
5457
});

src/mappers/mappers.interface.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { AirdropEvent } from '../types';
2+
import { DonV2 } from '../types/loading';
3+
import { WorkerAdapterOptions } from '../types/workers';
4+
5+
export interface MappersFactoryInterface {
6+
event: AirdropEvent;
7+
options?: WorkerAdapterOptions;
8+
}
9+
10+
export interface UpdateSyncMapperRecordParams {
11+
external_ids: {
12+
add: string[];
13+
};
14+
secondary_ids?: Record<string, string>;
15+
targets: {
16+
add: DonV2[];
17+
};
18+
status: SyncMapperRecordStatus;
19+
input_files?: {
20+
add: string[];
21+
};
22+
external_versions?: {
23+
add: SyncMapperRecordExternalVersion[];
24+
};
25+
extra_data?: string;
26+
}
27+
28+
export interface SyncMapperRecord {
29+
id: DonV2;
30+
external_ids: string[];
31+
secondary_ids?: Record<string, string>;
32+
targets: DonV2[];
33+
status: SyncMapperRecordStatus;
34+
input_files?: string[];
35+
external_versions?: SyncMapperRecordExternalVersion[];
36+
extra_data?: string;
37+
}
38+
39+
export interface MappersGetByTargetIdParams {
40+
sync_unit: DonV2;
41+
target: DonV2;
42+
}
43+
44+
export interface MappersGetByTargetIdResponse {
45+
sync_mapper_record: SyncMapperRecord;
46+
}
47+
48+
export interface MappersCreateParams {
49+
sync_unit: DonV2;
50+
external_ids: string[];
51+
secondary_ids?: Record<string, string>;
52+
targets: DonV2[];
53+
status: SyncMapperRecordStatus;
54+
input_files?: string[];
55+
external_versions?: SyncMapperRecordExternalVersion[];
56+
extra_data?: string;
57+
}
58+
59+
export interface MappersCreateResponse {
60+
sync_mapper_record: SyncMapperRecord;
61+
}
62+
63+
export interface MappersUpdateParams {
64+
id: DonV2;
65+
sync_unit: DonV2;
66+
external_ids: {
67+
add: string[];
68+
};
69+
secondary_ids?: Record<string, string>;
70+
targets: {
71+
add: DonV2[];
72+
};
73+
status: SyncMapperRecordStatus;
74+
input_files?: {
75+
add: string[];
76+
};
77+
external_versions?: {
78+
add: SyncMapperRecordExternalVersion[];
79+
};
80+
extra_data?: string;
81+
}
82+
83+
export interface MappersUpdateResponse {
84+
sync_mapper_record: SyncMapperRecord;
85+
}
86+
87+
export enum SyncMapperRecordStatus {
88+
OPERATIONAL = 'operational',
89+
FILTERED = 'filtered',
90+
IGNORED = 'ignored',
91+
}
92+
93+
export interface SyncMapperRecordExternalVersion {
94+
recipe_version: number;
95+
modified_date: string;
96+
}

src/mappers/mappers.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import axios, { AxiosResponse } from 'axios';
2+
3+
import {
4+
MappersFactoryInterface,
5+
MappersCreateParams,
6+
MappersCreateResponse,
7+
MappersGetByTargetIdParams,
8+
MappersGetByTargetIdResponse,
9+
MappersUpdateParams,
10+
MappersUpdateResponse,
11+
} from './mappers.interface';
12+
13+
export class Mappers {
14+
private endpoint: string;
15+
private token: string;
16+
17+
constructor({ event }: MappersFactoryInterface) {
18+
this.endpoint = event.execution_metadata.devrev_endpoint;
19+
this.token = event.context.secrets.service_account_token;
20+
}
21+
22+
async getByTargetId(
23+
params: MappersGetByTargetIdParams
24+
): Promise<AxiosResponse<MappersGetByTargetIdResponse>> {
25+
const { sync_unit, target } = params;
26+
return axios.get<MappersGetByTargetIdResponse>(
27+
`${this.endpoint}/internal/airdrop.sync-mapper-record.get-by-target`,
28+
{
29+
headers: {
30+
Authorization: this.token,
31+
},
32+
params: { sync_unit, target },
33+
}
34+
);
35+
}
36+
37+
async create(
38+
params: MappersCreateParams
39+
): Promise<AxiosResponse<MappersCreateResponse>> {
40+
return axios.post<MappersCreateResponse>(
41+
`${this.endpoint}/internal/airdrop.sync-mapper-record.create`,
42+
params,
43+
{
44+
headers: {
45+
Authorization: this.token,
46+
},
47+
}
48+
);
49+
}
50+
51+
async update(
52+
params: MappersUpdateParams
53+
): Promise<AxiosResponse<MappersUpdateResponse>> {
54+
return axios.post<MappersUpdateResponse>(
55+
`${this.endpoint}/internal/airdrop.sync-mapper-record.update`,
56+
params,
57+
{
58+
headers: {
59+
Authorization: this.token,
60+
},
61+
}
62+
);
63+
}
64+
}

0 commit comments

Comments
 (0)