From 514ddb85ba996b81c2d9d5346ade941b30bbc206 Mon Sep 17 00:00:00 2001 From: Advaith Krishna Date: Tue, 8 Apr 2025 16:05:55 +0530 Subject: [PATCH 1/5] last-coding-activity --- 14-operations/.DS_Store | Bin 0 -> 6148 bytes 14-operations/code/.npmrc | 4 +- 14-operations/code/package-lock.json | 43 ++++ 14-operations/code/package.json | 1 + .../fixtures/get_last_coding_activity.json | 3 + .../get_last_coding_activity.ts | 198 ++++++++++++++++++ .../operation_handler/get_temperature.ts | 60 ------ .../src/functions/operation_handler/index.ts | 8 +- .../post_comment_on_ticket.ts | 85 -------- .../operation_handler/send_slack_message.ts | 86 -------- 14-operations/manifest.yaml | 93 ++------ 11 files changed, 266 insertions(+), 315 deletions(-) create mode 100644 14-operations/.DS_Store create mode 100644 14-operations/code/src/fixtures/get_last_coding_activity.json create mode 100644 14-operations/code/src/functions/operation_handler/get_last_coding_activity.ts delete mode 100644 14-operations/code/src/functions/operation_handler/get_temperature.ts delete mode 100644 14-operations/code/src/functions/operation_handler/post_comment_on_ticket.ts delete mode 100644 14-operations/code/src/functions/operation_handler/send_slack_message.ts diff --git a/14-operations/.DS_Store b/14-operations/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..79f4da63d7ccc6916ebf57683494d6d8655ba3b3 GIT binary patch literal 6148 zcmeHK!EVz)5S>j^YOAW015{j)EOCuOf&@Y0;)e9VflH0x04UhC0Sm`l#SRgQBKb(a zrZ+x?-+?!~+lZuar4^cyX5a4Y%&zvWwc{ZoHJYabq7D&xP{x*z<`2U2tXHJvEW1F( zYdkOXX;SC}V_s0?<}<)|*QQg_lv1{Je@B?>{WM|@vi3_B)tS5J@5xz_S9QPtL$!9= zySMhdJ#XK87k$!;sE+G-HHv3%c=S^1BDw6x$*VkDOggup>9UUVGMkxzJk8+p_H|yS zdNI=TGObK(WG6h|^Cz9d<+9%m9uHUDU^#d+c-Re|42P?g@7=lo;OWWab1^OTH%q+2 zS83$H;U%1-vp&t2XjT@w{0JNSAJCLyDk-NAlu<=B_2?@sQ!2Ljxq$cY{P=W2$8FU*7bl6yg1tLEJh6ZVbfq%-tFNYFuwg3PC literal 0 HcmV?d00001 diff --git a/14-operations/code/.npmrc b/14-operations/code/.npmrc index cffe8cd..94f1d69 100644 --- a/14-operations/code/.npmrc +++ b/14-operations/code/.npmrc @@ -1 +1,3 @@ -save-exact=true +//npm.pkg.github.com/:_authToken=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 +@devrev:registry=https://npm.pkg.github.com/ +always-auth=true diff --git a/14-operations/code/package-lock.json b/14-operations/code/package-lock.json index 3fc44dc..13fad45 100644 --- a/14-operations/code/package-lock.json +++ b/14-operations/code/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@devrev/typescript-sdk": "1.1.28", + "@devrev/typescript-sdk-internal": "1.2.266", "@slack/web-api": "7.3.1", "axios": "1.7.9", "protobufjs": "7.3.0" @@ -1755,6 +1756,27 @@ "yargs": "^17.6.2" } }, + "node_modules/@devrev/typescript-sdk-internal": { + "version": "1.2.266", + "resolved": "https://npm.pkg.github.com/download/@devrev/typescript-sdk-internal/1.2.266/aab5b2ebdb0bb1c04027ca92737aabe9064fde64", + "integrity": "sha512-RJs2AsXPrZicZS3FyntAUwlLMaW0FChghd19zi8kk/ZgE29LuRWeTqmA9uyjkXFi9W7hzIxO/dRBCIhofE1S2A==", + "license": "ISC", + "dependencies": { + "@types/yargs": "^17.0.22", + "axios": "^1.7.4", + "dotenv": "^16.0.3", + "form-data": "^4.0.1", + "lru-cache": "^10.0.0", + "protobufjs": "^7.3.0", + "yargs": "^17.6.2" + } + }, + "node_modules/@devrev/typescript-sdk-internal/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -10554,6 +10576,27 @@ "yargs": "^17.6.2" } }, + "@devrev/typescript-sdk-internal": { + "version": "1.2.266", + "resolved": "https://npm.pkg.github.com/download/@devrev/typescript-sdk-internal/1.2.266/aab5b2ebdb0bb1c04027ca92737aabe9064fde64", + "integrity": "sha512-RJs2AsXPrZicZS3FyntAUwlLMaW0FChghd19zi8kk/ZgE29LuRWeTqmA9uyjkXFi9W7hzIxO/dRBCIhofE1S2A==", + "requires": { + "@types/yargs": "^17.0.22", + "axios": "^1.7.4", + "dotenv": "^16.0.3", + "form-data": "^4.0.1", + "lru-cache": "^10.0.0", + "protobufjs": "^7.3.0", + "yargs": "^17.6.2" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + } + } + }, "@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", diff --git a/14-operations/code/package.json b/14-operations/code/package.json index 6c63fab..04bf998 100644 --- a/14-operations/code/package.json +++ b/14-operations/code/package.json @@ -55,6 +55,7 @@ }, "dependencies": { "@devrev/typescript-sdk": "1.1.28", + "@devrev/typescript-sdk-internal": "1.2.266", "@slack/web-api": "7.3.1", "axios": "1.7.9", "protobufjs": "7.3.0" diff --git a/14-operations/code/src/fixtures/get_last_coding_activity.json b/14-operations/code/src/fixtures/get_last_coding_activity.json new file mode 100644 index 0000000..917afa1 --- /dev/null +++ b/14-operations/code/src/fixtures/get_last_coding_activity.json @@ -0,0 +1,3 @@ +{ + "object_id": "don:core:dvrv-us-1:devo/1LQbB5WUll:issue/18" +} \ No newline at end of file diff --git a/14-operations/code/src/functions/operation_handler/get_last_coding_activity.ts b/14-operations/code/src/functions/operation_handler/get_last_coding_activity.ts new file mode 100644 index 0000000..d9c76c3 --- /dev/null +++ b/14-operations/code/src/functions/operation_handler/get_last_coding_activity.ts @@ -0,0 +1,198 @@ +import { + OperationContext, + ExecuteOperationInput, + OperationOutput, + OutputValue, + FunctionInput, + OperationBase, +} from '@devrev/typescript-sdk/dist/snap-ins'; +import { + Api, + LinksListRequest, + TimelineEntriesListRequest, + LinkEndpointType, + ListMode, + TimelineChangeEvent, + TimelineEntryType, + WorkType, +} from '@devrev/typescript-sdk-internal/dist/auto-generated/internal/private-internal-devrev-sdk'; +import { internalSDK } from '@devrev/typescript-sdk-internal'; + +export interface GetInput { + object_id: string; +} + +export class GetLastCodingActivity extends OperationBase { + constructor(e: FunctionInput) { + super(e); + } + + async run( + context: OperationContext, + input: ExecuteOperationInput, + _resources: any, + ): Promise { + const inputData = input.data as GetInput; + + // Validate input + if (!inputData.object_id || inputData.object_id === '') { + return OperationOutput.fromJSON({ + error: { + message: 'No ID provided', + type: 'InvalidRequest', + }, + }); + } + + // Initialize the private DevRev SDK + const devrevSDK = new internalSDK.Api({ + headers: { + Authorization: context.secrets.access_token, + }, + baseURL: context.devrev_endpoint, + }) as Api; + + // Check if the object ID is that of an enhancement + let issueIDs: string[] = []; + if (inputData.object_id.toLowerCase().includes('enh')) { + const issueRequest = { + type: [WorkType.Issue], + applies_to_part: [inputData.object_id], + cursor: '', + }; + while (true) { + const issueResponse = await devrevSDK.worksList(issueRequest); + if (issueResponse.data?.works) { + for (const work of issueResponse.data.works) { + issueIDs.push(work.id); + } + } + if (!issueResponse.data?.next_cursor) { + break; + } + issueRequest.cursor = issueResponse.data.next_cursor; + } + } else { + issueIDs.push(inputData.object_id); + } + + let latestEpochTime = 0; + let codeChangeLinks: internalSDK.Link[] = []; + + for (const issueID of issueIDs) { + try { + // Step 1: Fetch linked code_change objects using links.list + const linksListRequest: LinksListRequest = { + object: issueID, + object_types: [LinkEndpointType.CodeChange], // Filter for code_change objects + }; + + while (true) { + const linksResponse = await devrevSDK.linksList(linksListRequest); + const entries = linksResponse.data?.links || []; + codeChangeLinks = [...codeChangeLinks, ...entries]; + const next_cursor = linksResponse.data?.next_cursor; + if (next_cursor) { + linksListRequest.cursor = next_cursor; + } else { + break; + } + } + + + //For each code_change object, traverse the timeline to get the latest coding activity + for (const link of codeChangeLinks) { + try { + const timelineRequest: TimelineEntriesListRequest = { + object: link.target.id, + mode: ListMode.Before, + }; + + while (true) { + const timelineResponse = + await devrevSDK.timelineEntriesList(timelineRequest); + for (const entry of timelineResponse.data?.timeline_entries || + []) { + if (entry.type == TimelineEntryType.TimelineChangeEvent) { + const timelineChangeEvent = entry as TimelineChangeEvent; + // Add null checks for nested properties + const eventMetadata = timelineChangeEvent.event?.annotated?.microflow_action?.event_metadata; + + // Safely access the source_time_stamp + let sourceTimeStamp: string | undefined; + if (eventMetadata?.keys) { + try { + // Use a safer approach to access the property + const keysObj = eventMetadata.keys as unknown as Record; + sourceTimeStamp = keysObj['source_time_stamp']; + } catch (error) { + console.error('Error accessing source_time_stamp:', error); + } + } + + if (sourceTimeStamp) { + const epochTime = new Date(sourceTimeStamp).getTime(); + if (epochTime && epochTime > latestEpochTime) { + latestEpochTime = epochTime; + } + } + } + } + + const next_cursor = timelineResponse.data?.next_cursor; + if (next_cursor) { + timelineRequest.cursor = next_cursor; + } else { + break; + } + } + } catch (error) { + return OperationOutput.fromJSON({ + error: { + message: `No valid timeline entries with source_time_stamp found for linked code changes of object ${inputData.object_id}`, + type: 'InvalidRequest', + }, + }); + } + } + } catch (error) { + return OperationOutput.fromJSON({ + error: { + message: 'No object ID provided', + type: 'InvalidRequest', // Ensure this is a string + }, + }); + } + } + + if (codeChangeLinks.length === 0) { + return OperationOutput.fromJSON({ + summary: `No code_change objects linked to ${inputData.object_id}`, + output: { + values: [{}], + } as OutputValue, + }); + } + + if (latestEpochTime === 0) { + return OperationOutput.fromJSON({ + summary: `No coding activity yet, linked to this object ${inputData.object_id}`, + output: { + values: [{}], + } as OutputValue, + }); + } + + let latestSrcTime = new Date(latestEpochTime).toISOString(); + return OperationOutput.fromJSON({ + summary: `Latest coding activity linked to ${inputData.object_id} happened at ${latestSrcTime}`, + output: { + values: [ + { + last_coding_activity: [latestSrcTime], + }, + ], + } as OutputValue, + }); + } +} diff --git a/14-operations/code/src/functions/operation_handler/get_temperature.ts b/14-operations/code/src/functions/operation_handler/get_temperature.ts deleted file mode 100644 index 475798d..0000000 --- a/14-operations/code/src/functions/operation_handler/get_temperature.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { client, publicSDK } from '@devrev/typescript-sdk'; -import { - Error as OperationError, - Error_Type, - ExecuteOperationInput, - FunctionInput, - OperationBase, - OperationContext, - OperationOutput, - OutputValue, -} from '@devrev/typescript-sdk/dist/snap-ins'; - -interface GetTemperatureInput { - city: string; -} - -export class GetTemperature extends OperationBase { - constructor(e: FunctionInput) { - super(e); - } - - // This is optional and can be used to provide any extra context required. - override GetContext(): OperationContext { - let baseMetadata = super.GetContext(); - const temperatures: Record = { - 'New York': 72, - 'San Francisco': 65, - Seattle: 55, - 'Los Angeles': 80, - Chicago: 70, - Houston: 90, - }; - - return { - ...baseMetadata, - metadata: temperatures, - }; - } - - async run(_context: OperationContext, input: ExecuteOperationInput, _resources: any): Promise { - const input_data = input.data as GetTemperatureInput; - - const temperature = _context.metadata ? _context.metadata[input_data.city] : null; - - let err: OperationError | undefined = undefined; - if (!temperature) { - err = { - message: 'City not found', - type: Error_Type.InvalidRequest, - }; - } - const temp = { - error: err, - output: { - values: [{ "temperature": temperature }], - } as OutputValue, - } - return OperationOutput.fromJSON(temp); - } -} diff --git a/14-operations/code/src/functions/operation_handler/index.ts b/14-operations/code/src/functions/operation_handler/index.ts index 32ce626..90a0568 100644 --- a/14-operations/code/src/functions/operation_handler/index.ts +++ b/14-operations/code/src/functions/operation_handler/index.ts @@ -2,18 +2,14 @@ import { OperationFactory } from '../../operations'; import { ExecuteOperationInput,FunctionInput, OperationMap } from '@devrev/typescript-sdk/dist/snap-ins'; // Operations -import { GetTemperature } from './get_temperature'; -import { PostCommentOnTicket } from './post_comment_on_ticket'; -import { SendSlackMessage } from './send_slack_message'; +import { GetLastCodingActivity } from './get_last_coding_activity'; /** * Map of operations with the slug mentioned in the manifest. * The key is the slug of the operation mentioned in the manifest and value is the operation class. */ const operationMap: OperationMap = { - get_temperature: GetTemperature, - post_comment_on_ticket: PostCommentOnTicket, - send_slack_message: SendSlackMessage, + get_last_coding_activity: GetLastCodingActivity, }; export const run = async (events: FunctionInput[]) => { diff --git a/14-operations/code/src/functions/operation_handler/post_comment_on_ticket.ts b/14-operations/code/src/functions/operation_handler/post_comment_on_ticket.ts deleted file mode 100644 index e8ef4c2..0000000 --- a/14-operations/code/src/functions/operation_handler/post_comment_on_ticket.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { client } from '@devrev/typescript-sdk'; -import { TimelineEntriesCreateRequestType } from '@devrev/typescript-sdk/dist/auto-generated/beta/beta-devrev-sdk'; -import { - Error as OperationError, - Error_Type, - ExecuteOperationInput, - FunctionInput, - OperationBase, - OperationContext, - OperationOutput, - OutputValue, -} from '@devrev/typescript-sdk/dist/snap-ins'; - -interface PostCommentOnTicketInput { - id: string; - comment: string; -} - -export class PostCommentOnTicket extends OperationBase { - constructor(e: FunctionInput) { - super(e); - } - - async run(context: OperationContext, input: ExecuteOperationInput, _resources: any): Promise { - const input_data = input.data as PostCommentOnTicketInput; - const ticket_id = input_data.id; - const comment = input_data.comment; - - let err: OperationError | undefined = undefined; - if (!ticket_id) { - err = { - message: 'Ticket ID not found', - type: Error_Type.InvalidRequest, - }; - } - - const endpoint = context.devrev_endpoint; - const token = context.secrets.access_token; - - const devrevBetaClient = client.setupBeta({ - endpoint: endpoint, - token: token, - }); - let ticket; - try { - const ticketResponse = await devrevBetaClient.worksGet({ - id: ticket_id, - }); - console.log(JSON.stringify(ticketResponse.data)); - ticket = ticketResponse.data.work; - } catch (e: any) { - err = { - message: 'Error while fetching ticket details:' + e.message, - type: Error_Type.InvalidRequest, - }; - return OperationOutput.fromJSON({ - error: err, - }); - } - - try { - const timelineCommentResponse = await devrevBetaClient.timelineEntriesCreate({ - body: comment, - type: TimelineEntriesCreateRequestType.TimelineComment, - object: ticket.id, - }); - console.log(JSON.stringify(timelineCommentResponse.data)); - let commentID = timelineCommentResponse.data.timeline_entry.id; - return OperationOutput.fromJSON({ - error: err, - output: { - values: [{ comment_id: commentID }], - } as OutputValue, - }); - } catch (e: any) { - err = { - message: 'Error while posting comment:' + e.message, - type: Error_Type.InvalidRequest, - }; - return OperationOutput.fromJSON({ - error: err, - }); - } - } -} diff --git a/14-operations/code/src/functions/operation_handler/send_slack_message.ts b/14-operations/code/src/functions/operation_handler/send_slack_message.ts deleted file mode 100644 index 91a0514..0000000 --- a/14-operations/code/src/functions/operation_handler/send_slack_message.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { client } from '@devrev/typescript-sdk'; -import { TimelineEntriesCreateRequestType } from '@devrev/typescript-sdk/dist/auto-generated/beta/beta-devrev-sdk'; -import { - Error as OperationError, - Error_Type, - ExecuteOperationInput, - FunctionInput, - OperationBase, - OperationContext, - OperationOutput, - OutputValue, -} from '@devrev/typescript-sdk/dist/snap-ins'; - -import { WebClient } from '@slack/web-api'; - -interface SendSlackMessageInput { - channel: string; - message: string; -} - -export class SendSlackMessage extends OperationBase { - constructor(e: FunctionInput) { - super(e); - } - async run(context: OperationContext, input: ExecuteOperationInput, resources: any): Promise { - const input_data = input.data as SendSlackMessageInput; - const channel_id = input_data.channel; - const comment = input_data.message; - - let err: OperationError | undefined = undefined; - if (!channel_id) { - err = { - message: 'Channel ID not found', - type: Error_Type.InvalidRequest, - }; - } - - console.log("context:", context); - - const slack_token = resources.keyrings.slack_token.secret; - let slackClient; - try { - console.log('Creating slack client'); - slackClient = new WebClient(slack_token); - console.log('Slack client created'); - } catch (e: any) { - console.log('Error while creating slack client:', e.message); - err = { - message: 'Error while creating slack client:' + e.message, - type: Error_Type.InvalidRequest, - }; - return OperationOutput.fromJSON({ - error: err, - output: { - values: [], - } as OutputValue, - }); - } - console.log('Sending message to slack channel:', channel_id); - try { - const result = await slackClient.chat.postMessage({ - channel: channel_id, - text: comment, - }); - console.log('Message sent: ', result.ts); - return OperationOutput.fromJSON({ - error: err, - output: { - values: [{ message_id: result.ts }], - } as OutputValue, - }); - } catch (e: any) { - console.log('Error while sending message:', e.message); - err = { - message: 'Error while sending message:' + e.message, - type: Error_Type.InvalidRequest, - }; - return OperationOutput.fromJSON({ - error: err, - output: { - values: [], - } as OutputValue, - }); - } - } -} diff --git a/14-operations/manifest.yaml b/14-operations/manifest.yaml index 7f9ebba..13c6223 100644 --- a/14-operations/manifest.yaml +++ b/14-operations/manifest.yaml @@ -1,98 +1,37 @@ version: "2" -name: "Operations" -description: "Pack of operations" +name: "Risk prediction" +description: "Risk prediction operations" service_account: - display_name: Operations Bot + display_name: Enhancement Retrieval functions: - name: operation_handler description: function to handle operations operations: - - name: get_temperature - display_name: Get Temperature - description: Operation to get the temperature of a city + - name: get_last_coding_activity + display_name: Last coding timestamp for an object + description: Node that retrieves the last coding activity of an issue or an enhancement slug: get_temperature function: operation_handler type: action # Inputs to the operation. inputs: fields: - - name: city - field_type: enum - allowed_values: - - New York - - San Francisco - - Los Angeles - - Chicago - - Houston + - name: object_id + description: Object ID to retrieve last coding activity for. + field_type: id + id_type: + - enhancement + - issue is_required: true - default_value: "New York" ui: - display_name: City + display_name: Object ID # Outputs of the operation. outputs: fields: - - name: temperature - field_type: double + - name: last_coding_activity + field_type: timestamp ui: - display_name: Temperature - - name: post_comment_on_ticket - display_name: Post Comment on Ticket - description: Operation to post a comment on ticket - slug: post_comment_on_ticket - function: operation_handler - type: action - inputs: - fields: - - name: id - description: Ticket ID to post comment on. - field_type: text - is_required: true - ui: - display_name: Ticket ID - - name: comment - description: Comment to post on ticket. - field_type: text - is_required: true - ui: - display_name: Comment - outputs: - fields: - - name: comment_id - field_type: text - ui: - display_name: Comment ID - - name: send_slack_message - display_name: Send Slack Message - description: Operation to send a message to a Slack channel/thread - slug: send_slack_message - function: operation_handler - type: action - keyrings: - - name: slack_token - display_name: Slack Connection - description: Connection to Slack - types: - - slack - inputs: - fields: - - name: channel - description: Channel to send message to. - field_type: text - is_required: true - ui: - display_name: Channel - - name: message - description: Message to send. - field_type: rich_text - is_required: true - ui: - display_name: Message - outputs: - fields: - - name: message_id - field_type: text - ui: - display_name: Message ID + display_name: Last coding activity From 1b143171c45dcd263228519fb643cea9ad7507d9 Mon Sep 17 00:00:00 2001 From: Advaith Krishna Date: Tue, 8 Apr 2025 16:10:02 +0530 Subject: [PATCH 2/5] PAT removed --- 14-operations/code/.npmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/14-operations/code/.npmrc b/14-operations/code/.npmrc index 94f1d69..6d36444 100644 --- a/14-operations/code/.npmrc +++ b/14-operations/code/.npmrc @@ -1,3 +1,3 @@ -//npm.pkg.github.com/:_authToken=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 +//npm.pkg.github.com/:_authToken=GITHUB_PAT_HERE @devrev:registry=https://npm.pkg.github.com/ always-auth=true From 0f65d29396517bbc16f2218b86fcd7526721cf30 Mon Sep 17 00:00:00 2001 From: Advaith Krishna Date: Wed, 9 Apr 2025 18:27:32 +0530 Subject: [PATCH 3/5] This is an action node that gets the last coding activity of an object --- .../get_last_coding_activity.ts | 103 +++++++++++------- 14-operations/manifest.yaml | 12 +- 2 files changed, 72 insertions(+), 43 deletions(-) diff --git a/14-operations/code/src/functions/operation_handler/get_last_coding_activity.ts b/14-operations/code/src/functions/operation_handler/get_last_coding_activity.ts index d9c76c3..7dafcf7 100644 --- a/14-operations/code/src/functions/operation_handler/get_last_coding_activity.ts +++ b/14-operations/code/src/functions/operation_handler/get_last_coding_activity.ts @@ -8,15 +8,17 @@ import { } from '@devrev/typescript-sdk/dist/snap-ins'; import { Api, - LinksListRequest, - TimelineEntriesListRequest, + LinksListParams, + TimelineEntriesListParams, LinkEndpointType, ListMode, TimelineChangeEvent, TimelineEntryType, WorkType, + WorksListParams, + Link, } from '@devrev/typescript-sdk-internal/dist/auto-generated/internal/private-internal-devrev-sdk'; -import { internalSDK } from '@devrev/typescript-sdk-internal'; +import { client } from '@devrev/typescript-sdk-internal'; export interface GetInput { object_id: string; @@ -43,25 +45,27 @@ export class GetLastCodingActivity extends OperationBase { }, }); } + console.log('Input object ID:', inputData.object_id); + console.log('Starting to get last coding activity for object ID:', inputData.object_id); // Initialize the private DevRev SDK - const devrevSDK = new internalSDK.Api({ - headers: { - Authorization: context.secrets.access_token, - }, - baseURL: context.devrev_endpoint, - }) as Api; - + const devrevSDK = client.setupInternal({ + endpoint: context.devrev_endpoint, + token: context.secrets.access_token, + }); + // Check if the object ID is that of an enhancement let issueIDs: string[] = []; if (inputData.object_id.toLowerCase().includes('enh')) { - const issueRequest = { + console.log('Object ID is an enhancement'); + const issueRequest: WorksListParams = { type: [WorkType.Issue], applies_to_part: [inputData.object_id], - cursor: '', }; while (true) { + console.log('Fetching issues for object:', inputData.object_id); const issueResponse = await devrevSDK.worksList(issueRequest); + //console.log('Issue response:', issueResponse); if (issueResponse.data?.works) { for (const work of issueResponse.data.works) { issueIDs.push(work.id); @@ -73,22 +77,25 @@ export class GetLastCodingActivity extends OperationBase { issueRequest.cursor = issueResponse.data.next_cursor; } } else { + console.log('Object ID is not an enhancement'); issueIDs.push(inputData.object_id); } let latestEpochTime = 0; - let codeChangeLinks: internalSDK.Link[] = []; + let codeChangeLinks: Link[] = []; for (const issueID of issueIDs) { try { // Step 1: Fetch linked code_change objects using links.list - const linksListRequest: LinksListRequest = { + const linksListRequest: LinksListParams = { object: issueID, - object_types: [LinkEndpointType.CodeChange], // Filter for code_change objects + object_types: [LinkEndpointType.CodeChange], }; while (true) { + console.log('Fetching links for object:', issueID); const linksResponse = await devrevSDK.linksList(linksListRequest); + console.log('Links response:', linksResponse); const entries = linksResponse.data?.links || []; codeChangeLinks = [...codeChangeLinks, ...entries]; const next_cursor = linksResponse.data?.next_cursor; @@ -98,12 +105,12 @@ export class GetLastCodingActivity extends OperationBase { break; } } - - + + //For each code_change object, traverse the timeline to get the latest coding activity for (const link of codeChangeLinks) { try { - const timelineRequest: TimelineEntriesListRequest = { + const timelineRequest: TimelineEntriesListParams = { object: link.target.id, mode: ListMode.Before, }; @@ -111,26 +118,46 @@ export class GetLastCodingActivity extends OperationBase { while (true) { const timelineResponse = await devrevSDK.timelineEntriesList(timelineRequest); + console.log('Timeline response:', timelineResponse); for (const entry of timelineResponse.data?.timeline_entries || []) { if (entry.type == TimelineEntryType.TimelineChangeEvent) { const timelineChangeEvent = entry as TimelineChangeEvent; // Add null checks for nested properties - const eventMetadata = timelineChangeEvent.event?.annotated?.microflow_action?.event_metadata; - + const event = timelineChangeEvent.event; + // Safely access the source_time_stamp let sourceTimeStamp: string | undefined; - if (eventMetadata?.keys) { + if (event) { try { - // Use a safer approach to access the property - const keysObj = eventMetadata.keys as unknown as Record; - sourceTimeStamp = keysObj['source_time_stamp']; + // Use a safer approach to access the property by traversing the event object + const eventObj = event as unknown as Record; + + // Check if it's an annotated event with microflow_action + if (eventObj['annotated']?.['microflow_action']?.['event_metadata']) { + const metadata = eventObj['annotated']['microflow_action']['event_metadata']; + // Find the source_time_stamp in the metadata array + const timeStampEntry = metadata.find((item: any) => item.key === 'source_time_stamp'); + if (timeStampEntry) { + sourceTimeStamp = timeStampEntry.value; + } + } + + // Fallback to previous methods if needed + if (!sourceTimeStamp) { + sourceTimeStamp = eventObj['source_time_stamp'] || + eventObj['data']?.['source_time_stamp'] || + eventObj['metadata']?.['keys']?.['source_time_stamp']; + } + + console.log('Source time stamp:', sourceTimeStamp); } catch (error) { console.error('Error accessing source_time_stamp:', error); } } - + if (sourceTimeStamp) { + console.log('Source time stamp:', sourceTimeStamp); const epochTime = new Date(sourceTimeStamp).getTime(); if (epochTime && epochTime > latestEpochTime) { latestEpochTime = epochTime; @@ -147,50 +174,44 @@ export class GetLastCodingActivity extends OperationBase { } } } catch (error) { - return OperationOutput.fromJSON({ - error: { - message: `No valid timeline entries with source_time_stamp found for linked code changes of object ${inputData.object_id}`, - type: 'InvalidRequest', - }, - }); + console.error('Error fetching timeline entries:', error); } } } catch (error) { - return OperationOutput.fromJSON({ - error: { - message: 'No object ID provided', - type: 'InvalidRequest', // Ensure this is a string - }, - }); + console.error('Error fetching links:', error); } } - - if (codeChangeLinks.length === 0) { + if (codeChangeLinks.length === 0) { + console.log('No code_change objects linked to this object'); return OperationOutput.fromJSON({ summary: `No code_change objects linked to ${inputData.object_id}`, output: { - values: [{}], + values: [{ justification: `No code_change objects linked to this object ${inputData.object_id}` }], } as OutputValue, }); } if (latestEpochTime === 0) { + console.log('No coding activity found for this object'); return OperationOutput.fromJSON({ summary: `No coding activity yet, linked to this object ${inputData.object_id}`, output: { - values: [{}], + values: [{ justification: `No coding activity yet, linked to this object ${inputData.object_id}` }], } as OutputValue, }); } let latestSrcTime = new Date(latestEpochTime).toISOString(); + console.log('Latest coding activity time:', latestSrcTime); return OperationOutput.fromJSON({ summary: `Latest coding activity linked to ${inputData.object_id} happened at ${latestSrcTime}`, output: { values: [ { last_coding_activity: [latestSrcTime], + justification: `Latest coding activity linked to ${inputData.object_id} happened at ${latestSrcTime}`, }, + ], } as OutputValue, }); diff --git a/14-operations/manifest.yaml b/14-operations/manifest.yaml index 13c6223..92bfda5 100644 --- a/14-operations/manifest.yaml +++ b/14-operations/manifest.yaml @@ -3,7 +3,7 @@ name: "Risk prediction" description: "Risk prediction operations" service_account: - display_name: Enhancement Retrieval + display_name: Latest coding activity functions: - name: operation_handler @@ -13,7 +13,7 @@ operations: - name: get_last_coding_activity display_name: Last coding timestamp for an object description: Node that retrieves the last coding activity of an issue or an enhancement - slug: get_temperature + slug: get_last_coding_activity function: operation_handler type: action # Inputs to the operation. @@ -28,6 +28,7 @@ operations: is_required: true ui: display_name: Object ID + # Outputs of the operation. outputs: fields: @@ -35,3 +36,10 @@ operations: field_type: timestamp ui: display_name: Last coding activity + - name: justification + description: Justification for the operation. + field_type: text + is_required: true + ui: + display_name: Justification + placeholder: Justification for the operation From bd0fafb35f7bc150721fef434a9fb410c8be92ab Mon Sep 17 00:00:00 2001 From: Advaith Krishna Date: Wed, 9 Apr 2025 18:54:10 +0530 Subject: [PATCH 4/5] Nit --- 14-operations/code/.npmrc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/14-operations/code/.npmrc b/14-operations/code/.npmrc index 6d36444..cffe8cd 100644 --- a/14-operations/code/.npmrc +++ b/14-operations/code/.npmrc @@ -1,3 +1 @@ -//npm.pkg.github.com/:_authToken=GITHUB_PAT_HERE -@devrev:registry=https://npm.pkg.github.com/ -always-auth=true +save-exact=true From 903b219f172bc32ed25f85ad9e96a1a10f309b73 Mon Sep 17 00:00:00 2001 From: Advaith Krishna Date: Wed, 9 Apr 2025 19:02:20 +0530 Subject: [PATCH 5/5] Nit --- 14-operations/code/src/fixtures/get_last_coding_activity.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 14-operations/code/src/fixtures/get_last_coding_activity.json diff --git a/14-operations/code/src/fixtures/get_last_coding_activity.json b/14-operations/code/src/fixtures/get_last_coding_activity.json deleted file mode 100644 index 917afa1..0000000 --- a/14-operations/code/src/fixtures/get_last_coding_activity.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "object_id": "don:core:dvrv-us-1:devo/1LQbB5WUll:issue/18" -} \ No newline at end of file