Gathering detailed insights and metrics for @valiantys/atlassian-app-backend
Gathering detailed insights and metrics for @valiantys/atlassian-app-backend
Gathering detailed insights and metrics for @valiantys/atlassian-app-backend
Gathering detailed insights and metrics for @valiantys/atlassian-app-backend
npm install @valiantys/atlassian-app-backend
Typescript
Module System
Node Version
NPM Version
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
4
This library provides utility functions to handle all the setup necessary to support a Forge app backend that can run deployed or in standalone mode.
Note: The library currently only supports resolver functions and Forge Storage in standalone mode (without a tunnel).
1npm install -g @forge/cli@latest 2forge create -d forge-app -t jira-global-page-custom-ui
Install the necessary libraries and initialize typescript compiler config.
1npm i --save-dev typescript nodemon 2npm i dotenv 3npx tsc --init
Add "exclude" and "outDir" to tsconfig.json:
1{ 2 "exclude": ["node_modules", "dist", "static", "**/*spec.ts"], 3 "compilerOptions": { 4 "outDir": "dist", 5 ... 6 } 7}
Add scripts to package.json.
1{ 2 "scripts": { 3 "build": "tsc", 4 "dev": "tsc -w & nodemon -q -w dist dist/index.js", 5 "start": "tsc && node ./dist/index.js" 6 } 7}
1npm i @valiantys/atlassian-app-backend
Add a .env file in the root of the project, and add .env to your project's .gitignore file. You should also add a sample.env file to your project that does NOT contain any secrets but makes it easier for other developers to set up their own .env file.
1IS_STANDALONE=true 2 3# Needed for standalone authentication with Atlassian OAuth 4ATLASSIAN_OAUTH_CLIENT_ID=<client-id> 5ATLASSIAN_OAUTH_CLIENT_SECRET=<client-secret> 6 7# Needed for mock forge storage implementation 8FORGE_STORAGE_FILE_PATH= 9FORGE_STORAGE_INDEX_FIELD_MAPPING=
See https://developer.atlassian.com/cloud/jira/software/oauth-2-3lo-apps/#enabling-oauth-2-0--3lo- for instructions on creating an OAuth App to use for authentication. The callback url will be http://localhost:4200/callback if your UI module is running locally on port 4200.
Note: If you will be running more than one frontend app locally at the same time, you will need to configure a different OAuth App for each frontend port number (4200, 4201, etc.), and make sure they consistently choose the same port.
Rename src/index.js to index.ts and replace contents with:
1import { bootstrapExpress, expressDefaults, registerFunctions } from '@valiantys/atlassian-app-backend'; 2 3import Resolver from '@forge/resolver'; 4import * as dotenv from 'dotenv'; 5 6import { getForgeFunctions } from './lib/forge-functions'; 7import { getStandaloneFunctions } from './lib/standalone-functions'; 8 9dotenv.config(); 10 11const IS_STANDALONE = process.env.IS_STANDALONE === 'true'; 12 13if (IS_STANDALONE) { 14 const port = parseInt(process.env.PORT || '3000'); 15 16 // OAuth settings 17 const clientId = process.env.ATLASSIAN_OAUTH_CLIENT_ID || ''; 18 const clientSecret = process.env.ATLASSIAN_OAUTH_CLIENT_SECRET || ''; 19 20 // Mock Forge storage settings 21 const filePath = process.env.FORGE_STORAGE_FILE_PATH || ''; 22 const indexFieldMapping = JSON.parse(process.env.FORGE_STORAGE_INDEX_FIELD_MAPPING || '{}'); 23 24 bootstrapExpress(getStandaloneFunctions(), 'jira', { clientId, clientSecret }, { ...expressDefaults, port }, { indexFieldMapping, filePath }); 25} 26 27let resolver: Resolver | null = null; 28if (!IS_STANDALONE) { 29 resolver = new Resolver(); 30 registerFunctions(resolver, getForgeFunctions()); 31} 32export const handler = resolver?.getDefinitions();
By abstracting out Forge dependencies into services, we are able to run the same functions in both the deployed Forge environment and in a standalone mode. In the example below, you will see that these functions are defined in the handler-functions.ts file, and depend on environment-specific services that are passed in. These services are initialized when the index file logic calls either the getForgeFunctions() or the getStandaloneFunctions() method based on the IS_STANDALONE environment variable.
1import { storage, startsWith, WhereConditions } from '@forge/api'; 2import { createServerSideForgeProductFetchService, ResolverFunctionMap } from '@valiantys/atlassian-app-backend'; 3 4import { handlerFunctions } from './handler-functions'; 5 6export function getForgeFunctions(): ResolverFunctionMap { 7 const productFetchService = createServerSideForgeProductFetchService('user', 'jira'); 8 const forgeStorageService = { 9 storage, 10 WhereConditions, 11 startsWith, 12 }; 13 14 // Here you could initialize any other deployed-specific services or config 15 // to pass to handlerFunctions as needed. 16 return handlerFunctions(productFetchService, forgeStorageService); 17}
The getStandaloneFunctions() function actually returns a function that is called by the library's express.js application setup. The library code will handle the details of passing in an implementation of a fetch service and the forge storage service. If you are targeting Confluence or Bitbucket instead of Jira, simply change the type parameter you see below.
1import { AtlassianProductFetchService, ForgeStorageServiceStandalone, ResolverFunctionFactory } from '@valiantys/atlassian-app-backend'; 2import { handlerFunctions } from './handler-functions'; 3 4export function getStandaloneFunctions(): ResolverFunctionFactory<'jira'> { 5 return (productFetchService: AtlassianProductFetchService<'jira'>, forgeStorageService: ForgeStorageServiceStandalone) => { 6 // Here you could initialize any other standalone-specific services or config 7 // to pass to handlerFunctions as needed. 8 return handlerFunctions(productFetchService, forgeStorageService); 9 }; 10}
This file contains generic function definitions that can be used in either environment. All environment-specific configuration or implementation should be passed in as parameters.
1import { AtlassianProductFetchService, ForgeRequest, ForgeStorageService } from '@valiantys/atlassian-app-backend'; 2 3// Add any additional environment specific config/services as parameters 4// to handlerFunctions() 5export function handlerFunctions(productFetchService: AtlassianProductFetchService<'jira'>, forgeStorageService: ForgeStorageService) { 6 return { 7 getText: { 8 function: () => { 9 return { text: 'Hello world!' }; 10 }, 11 }, 12 13 /* 14 Making an Atlassian product API request (@forge/api requestJira) 15 */ 16 getIssueTypes: { 17 function: () => 18 productFetchService.fetch<IssueTypeDetails[]>({ 19 url: productFetchService.route`/rest/api/2/issuetype`, 20 method: 'GET', 21 }), 22 }, 23 24 /** 25 * Forge storage examples 26 */ 27 getMyName: { 28 function: async () => { 29 const name = await forgeStorageService.storage.get('myName'); 30 return { name }; 31 }, 32 }, 33 setMyName: { 34 function: async ({ payload }: ForgeRequest<{ name: string }>) => { 35 await forgeStorageService.storage.set('myName', payload.name); 36 const name = await forgeStorageService.storage.get('myName'); 37 return { name }; 38 }, 39 updatesForgeStorage: true, // triggers save of local file when in standalone mode 40 }, 41 42 /** 43 * This example demonstrates how one can access the user's account ID 44 * in either deployed or standalone mode. 45 * This context is made available on every backend request. 46 */ 47 whoAmI: { 48 function: async ({ context }: ForgeRequest<void>) => { 49 console.log(context.accountId); 50 return { myId: context.accountId }; 51 }, 52 }, 53 }; 54} 55 56interface IssueTypeDetails { 57 avatarId: number; 58 description: string; 59 hierarchyLevel: number; 60 iconUrl: string; 61 id: string; 62 name: string; 63 self: string; 64 subtask: boolean; 65}
See the README file in the @valiantys/atlassian-app-frontend library for instructions on adding a Custom UI module to your app.
1npm run dev
When running in standalone mode, the app will be using an in-memory implementation of the Forge storage API, which persists changes to a local json file. When the app starts up it will check for an existing json file and load it into memory. Every time a resolver function completes, the json file will be updated if the function was marked with updatesForgeStorage:true. The location of the storage.json file is controlled by the environment variable FORGE_STORAGE_FILE_PATH, which should be set in your .env file.
1forge deploy
1forge install --site <your-site-name>.atlassian.net --product=jira --no-interactive --environment <env-name>
By default, the standalone backend allows cross-origin requests from http://localhost:4200 and http://localhost:4201. To change this configuration, you may pass in a custom origin configuration to the bootstrapExpress function in index.ts.
Example:
1bootstrapExpress(getStandaloneFunctions(), 'jira', { clientId, clientSecret }, { ...expressDefaults, port, corsOptions: { origin: 'http://example.com' } }, { indexFieldMapping, filePath });
You can pass in any CORS options as defined here: https://www.npmjs.com/package/cors#configuration-options
1npm i -D jest ts-jest @types/jest
Add a jest.config.js file in the project root directory.
1/** @type {import('ts-jest').JestConfigWithTsJest} **/ 2module.exports = { 3 modulePathIgnorePatterns: ['<rootDir>/static/'], 4 testEnvironment: 'node', 5 transform: { 6 '^.+.ts?$': ['ts-jest', {}], 7 }, 8};
Exclude this file in eslint.config.mjs.
1{ignores: ["static/*", "dist/*", "jest.config.js"]},
1import { handlerFunctions } from './handler-functions'; 2import { atlassianOAuthJiraFetch, AtlassianProductFetchService, ForgeStorageServiceStandalone, ForgeStorageStandaloneData } from '@valiantys/atlassian-app-backend'; 3 4describe('handlerFunctions', () => { 5 let storage: ForgeStorageStandaloneData; 6 let forgeStorageService: ForgeStorageServiceStandalone; 7 let productFetchService: AtlassianProductFetchService<'jira'>; 8 9 beforeEach(() => { 10 // use in-memory forge service for testing 11 storage = { 12 secrets: {}, 13 keyValues: {}, 14 entityCollections: {}, 15 }; 16 forgeStorageService = new ForgeStorageServiceStandalone({}, storage); 17 18 // Mock out fetch for testing 19 productFetchService = atlassianOAuthJiraFetch('test-token', 'test-site'); 20 productFetchService.fetch = jest.fn(); 21 }); 22 23 it('getText should return a text value', () => { 24 const response = handlerFunctions(productFetchService, forgeStorageService).getText.function(); 25 expect(response).toEqual({ 26 text: 'Hello world!', 27 }); 28 }); 29});
You will find an examples directory under node_modules/@valiantys/atlassian-app-backend/examples, containing all the examples shown in this README along with more supporting files. The repo for the full example application is also available at https://bitbucket.org/oasisdigital/atlassian-app-examples.
If you would like to clone a template repo to get started more quickly, you can find one at https://bitbucket.org/oasisdigital/atlassian-app-template.
Our issue-tracking board is viewable at https://trello.com/b/aRmPXQjq/atlassian-app-frontend-backend-npm-package-issue-tracker. To file an issue you may send it in an email to zacharykipping+wzolmxklz7ysfqky4alz@boards.trello.com.
You may also contact us with questions at forge@valiantys.com.
No vulnerabilities found.
No security vulnerabilities found.