Snap-in developmentTutorials

Using a snap-in to perform an external action

Introduction

In this tutorial, you’ll lean how to develop a snap-in that mirrors an issue from DevRev to GitHub. This requires addition of a command that can be run from the Discussions tab of an issue in DevRev, which creates an issue in GitHub.

Background context

  1. GitHub APIs mastery:

    • Delve into understanding GitHub APIs.
    • Learn how to fetch details and perform actions using GitHub APIs.
  2. Secrets and keyrings in DevRev snap-ins:

    • Explore the usage of secrets and refer to keyrings in DevRev snap-ins.
  3. Command creation and utilization in DevRev:

    • Gain proficiency in creating and using commands in DevRev.

Installation guide

If you did not follow the getting started tutorial then follow these steps to authenticate and initialize the snap-in TypeScript template:

$devrev profiles authenticate -o <dev-org-slug> -u <youremail@yourdomain.com>
$devrev snap_in_version init

Trigger

To initiate the process of creating a GitHub issue directly from a DevRev issue, a trigger mechanism is essential. In this context, the implementation involves the introduction of a specialized command. This command is designed to be executed exclusively from the discussion section of a DevRev issue, serving as the catalyst for replicating the issue on GitHub.

Action

The primary action involves leveraging the issue creation API provided by GitHub. This API is utilized to create the GitHub issue seamlessly from the corresponding issue in DevRev.

Creating the snap-in

Updating the manifest

To outline the structure of the snap-in, the initial step is to define key attributes in the snap-in’s manifest. Begin by specifying the name, description, and account display name for the snap-in.

1version: "2"
2name: "GitHub issue creator"
3description: "Replicate DevRev issues in GitHub."
4
5service_account:
6 display_name: GitHub Issue Creator

Keyrings

To facilitate authentication for our API calls, the initial step involves creating a Personal Access Token (PAT) in GitHub. This PAT can be stored as a connection within DevRev. Subsequently, this connection is employed within our snap-in in the form of keyrings.

1keyrings:
2 organization:
3 - name: github_connection
4 display_name: Github Connection
5 description: Github PAT
6 types:
7 - snap_in_secret

Functions and commands

Having established the foundational configurations, the subsequent step is to define the functions and commands responsible for orchestrating the core logic of the snap-in.

1functions:
2 - name: command_handler
3 description: Function to create a GitHub issue

The command clearly states where you can use it. For example, in the Discussions tab of issues.

It also explains the different situations and ways in which you can make use of this command.

1commands:
2 - name: gh_issue
3 namespace: devrev
4 description: Command to create a GitHub issue.
5 surfaces:
6 - surface: discussions
7 object_types:
8 - issue
9 usage_hint: "[OrgName] [RepoName]"
10 function: command_handler

To utilize this command, execute /gh_issue OrgName RepoName in the Discussions tab of the issue. Within the function logic, validations are implemented to ensure the correctness of both the organization name (OrgName) and repository name (RepoName) before proceeding with the issue creation.

Function logic

After creating the manifest and establishing the snap-in’s logic, the next step is to define the function logic that handles business logic. Once you understand the payload structure of a command, you can proceed with its implementation.

To proceed, define the handler function for command events.

1const handleEvent = async (event: any) => {
2 // Function logic goes here.
3};

Within this handler, the initial step involves extracting the GitHub token provided as input in the keyring. Subsequently, the Octokit, responsible for managing GitHub API requests, is initialized:

1const githubPAT = event.input_data.keyrings.github_connection;
2const octokit = new Octokit({
3 auth: githubPAT,
4});

To facilitate authentication for DevRev API calls, a DevRev token is required. The initialization process for the DevRev SDK involves using the received endpoint.

1const devrevToken = event.context.secrets.service_account_token;
2const endpoint = event.execution_metadata.devrev_endpoint;
3const devrevSDK = client.setup({
4 endpoint: endpoint,
5 token: devrevToken,
6});

With the SDKs successfully initialized, the next step is to invoke the necessary APIs.

As a preliminary step, the required fields for creating a GitHub Issue, namely title and body, need to be extracted. These details are sourced from the issue. To facilitate this, introduce a function defined for this specific purpose:

1const getIssueDetails = async (workId: string, devrevSDK: any) => {
2 try {
3 // Get the issue details from the database using the workId
4 const workItemResp = await devrevSDK.worksGet({
5 id: workId,
6 });
7 const workItem = workItemResp.data.work;
8
9 // Populate the issue details
10 const issueDetails = {
11 description: workItem.body,
12 issueDisplayName: workItem.display_id,
13 title: workItem.title,
14 };
15 return issueDetails;
16 } catch (error) {
17 console.error(error);
18 throw new Error("Failed to get issue details");
19 }
20};

Now, the function can be called within the handler function to obtain the necessary issue details::

1const workId = event.payload.source_id;
2const issueDetails = await getIssueDetails(workId, devrevSDK);

Following this, the next step involves parsing the parameters Organization and Repository Name that were passed in the command:

1const getOrgAndRepoNames = (paramString: string): string[] => {
2 const paramList = paramString.split(" ");
3 if (paramList.length !== 2) {
4 throw new Error("Invalid Parameters");
5 }
6 const [orgName, repoName] = paramList;
7 return [orgName, repoName];
8};

The GitHub REST API (https://docs.github.com/en/rest/orgs/orgs?apiVersion=2022-11-28#get-an-organization) is used to confirm the specified organisation name.

1const verifyOrgName = async (orgName: string, octokit: Octokit) => {
2 try {
3 await octokit.request("GET /orgs/{org}", {
4 headers: {
5 "X-GitHub-Api-Version": "2022-11-28",
6 },
7 org: orgName,
8 });
9 } catch (error) {
10 console.error(error);
11 throw new Error("Invalid Organisation Name");
12 }
13};

Similarly, the GET Repository is used to validate whether the entered repository name is correct.

1const verifyRepoName = async (
2 orgName: string,
3 repoName: string,
4 octokit: Octokit
5) => {
6 try {
7 await octokit.request("GET /repos/{owner}/{repo}", {
8 headers: {
9 "X-GitHub-Api-Version": "2022-11-28",
10 },
11 owner: orgName,
12 repo: repoName,
13 });
14 } catch (error) {
15 console.error(error);
16 throw new Error("Invalid Repository Name");
17 }
18};

After completing all the necessary verifications, the POST create issue can be invoked to create a new issue.

1const createGitHubIssue = async (
2 orgName: string,
3 repoName: string,
4 issueDetails: any,
5 octokit: Octokit
6) => {
7 try {
8 await octokit.request("POST /repos/{owner}/{repo}/issues", {
9 body: issueDetails.description,
10 headers: {
11 "X-GitHub-Api-Version": "2022-11-28",
12 },
13 owner: orgName,
14 repo: repoName,
15 title: "[" + issueDetails.issueDisplayName + "] " + issueDetails.title,
16 });
17 } catch (error) {
18 console.error(error);
19 throw new Error("Failed to create issue");
20 }
21};

The completion of functional logic is a key milestone in the process. After laying the framework, now you’ll learn how to install this snap-in for your organization.

Deploying the snap-in to your organization

Once the code has been validated, the next steps involve creating the snap-in version and subsequently creating the snap-in itself. Before installing the snap-in, it’s essential to set up a GitHub Personal Access Token (PAT) and add it to the connections in DevRev as a snap-in secret. Ensure that the secret is shared within the organization so that the snap-in can utilize it.

Follow these steps to install the snap-in in your organization:

Step 1: Create a GitHub personal access token

Generate a GitHub Personal Access Token (PAT) by following the steps outlined in the GitHub documentation.

Step 2: Add PAT to DevRev connections

Add the generated PAT as a snap-in secret in DevRev. This secret will be used during the installation of the snap-in. Ensure that the secret is shared within the organization to allow the snap-in to access it.

Step 3: Install the snap-in

During the installation of the snap-in, utilize the shared secret to authenticate and connect with GitHub. This ensures that the snap-in has the necessary permissions to interact with GitHub APIs.

Resources

The final snap-in code and manifest can be found here.