@axis-backstage/plugin-jira-dashboard-backend 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Jira Dashboard Backend
2
+
3
+ A plugin that makes requests to [Atlassian REST API](https://developer.atlassian.com/server/jira/platform/rest-apis/) to get issues and project information from Jira.
4
+
5
+ The frontend plugin that displays this information is [Jira Dashboard](https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/jira-dashboard).
6
+
7
+ ## Setup
8
+
9
+ The following sections will help you get the Jira Dashboard Backend plugin setup and running.
10
+
11
+ ### Installation
12
+
13
+ Install the plugin by following the example below:
14
+
15
+ ```bash
16
+ # From your Backstage root directory
17
+ yarn add --cwd packages/backend @axis-backstage/plugin-jira-dashboard-backend
18
+ ```
19
+
20
+ ### Configuration
21
+
22
+ The Jira Dashboard plugin requires the following YAML to be added to your app-config.yaml:
23
+
24
+ ```yaml
25
+ jiraDashboard:
26
+ token: ${JIRA_TOKEN}
27
+ baseUrl: ${JIRA_BASE_URL}'
28
+ userEmailSuffix: ${JIRA_EMAIL_SUFFIX}'
29
+ ```
30
+
31
+ Configuration Details:
32
+
33
+ - `JIRA_TOKEN`: The API token to authenticate towards Jira. It can be found by visiting Atlassians page at https://developer.atlassian.com/cloud/jira/platform/basic-auth-for-rest-apis/. In case you are using a Bearer or Basic token, you need to add it in the beginning of the token. For instance: `Bearer your-secret-token`
34
+ > Note: The JIRA_TOKEN variable from [Roadie's Backstage Jira plugin](https://roadie.io/backstage/plugins/jira) can not be reused here because of the added encoding in this token.
35
+ - `JIRA_BASE_URL`: The base url for Jira in your company, including the API version. For instance: https://jira.se.your-company.com/rest/api/2/'
36
+ - `JIRA_EMAIL_SUFFIX`: The email suffix used for retrieving a specific Jira user in a company. For instance: @your-company.com
37
+
38
+ ### Integrating
39
+
40
+ Here's how to get the backend plugin up and running:
41
+
42
+ 1. Create a new file named `packages/backend/src/plugins/jiraDashboard.ts`, and add the following to it:
43
+
44
+ ```ts
45
+ import { createRouter } from '@axis-backstage/plugin-jira-dashboard-backend';
46
+ import { Router } from 'express';
47
+ import { PluginEnvironment } from '../types';
48
+
49
+ export default async function createPlugin(
50
+ env: PluginEnvironment,
51
+ ): Promise<Router> {
52
+ return await createRouter({
53
+ logger: env.logger,
54
+ config: env.config,
55
+ discovery: env.discovery,
56
+ identity: env.identity,
57
+ tokenManager: env.tokenManager,
58
+ });
59
+ }
60
+ ```
61
+
62
+ 2. Wire this into the overall backend router by adding the following to `packages/backend/src/index.ts`:
63
+
64
+ ```ts
65
+ import jiraDashboard from './plugins/jiraDashboard';
66
+ ...
67
+
68
+ async function main() {
69
+ // Add this line under the other lines that follow the useHotMemoize pattern
70
+ const jiraDashboardEnv = useHotMemoize(module, () => createEnv('jira-dashboard'),
71
+
72
+ // Add this under the lines that add their routers to apiRouter
73
+ apiRouter.use('/jira-dashboard', await jiraDashboard(jiraDashboardEnv));
74
+ }
75
+ ```
76
+
77
+ 3. Now run `yarn start-backend` from the repo root.
78
+
79
+ 4. In another terminal, run the command: `curl localhost:7007/api/jira-dashboard/health`. The request should return `{"status":"ok"}`.
80
+
81
+ ### New Backend System
82
+
83
+ The Jira Dashboard backend plugin has support for the [new backend system](https://backstage.io/docs/backend-system/). Here is how you can set it up:
84
+
85
+ In your `packages/backend/src/index.ts` make the following changes:
86
+
87
+ ```diff
88
+ + import { jiraDashboardPlugin } from '@axis-backstage/plugin-jira-dashboard-backend';
89
+
90
+ const backend = createBackend();
91
+ + backend.add(jiraDashboardPlugin());
92
+ // ... other feature additions
93
+
94
+ backend.start();
95
+ ```
@@ -0,0 +1,341 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var backendCommon = require('@backstage/backend-common');
6
+ var express = require('express');
7
+ var Router = require('express-promise-router');
8
+ var catalogClient = require('@backstage/catalog-client');
9
+ var pluginJiraDashboardCommon = require('@axis-backstage/plugin-jira-dashboard-common');
10
+ var stream = require('stream');
11
+ var fetch = require('node-fetch');
12
+ var backendPluginApi = require('@backstage/backend-plugin-api');
13
+
14
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
15
+
16
+ var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
17
+ var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
18
+ var stream__default = /*#__PURE__*/_interopDefaultLegacy(stream);
19
+ var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
20
+
21
+ const JIRA_BASE_URL_CONFIG_PATH = "jiraDashboard.baseUrl";
22
+ const JIRA_TOKEN_CONFIG_PATH = "jiraDashboard.token";
23
+ const JIRA_USER_CONFIG_EMAIL_SUFFIX = "jiraDashboard.userEmailSuffix";
24
+ function resolveJiraBaseUrl(config) {
25
+ try {
26
+ return config.getString(JIRA_BASE_URL_CONFIG_PATH);
27
+ } catch (error) {
28
+ throw new Error(`Invalid Jira baseUrl, ${error}`);
29
+ }
30
+ }
31
+ function resolveJiraToken(config) {
32
+ try {
33
+ return config.getString(JIRA_TOKEN_CONFIG_PATH);
34
+ } catch (error) {
35
+ throw new Error(`Invalid Jira token, ${error}`);
36
+ }
37
+ }
38
+ function resolveUserEmailSuffix(config) {
39
+ try {
40
+ return config.getString(JIRA_USER_CONFIG_EMAIL_SUFFIX);
41
+ } catch (error) {
42
+ throw new Error(`Invalid Jira user path, ${error}`);
43
+ }
44
+ }
45
+
46
+ const getUsernameFromRef = (userRef) => {
47
+ return userRef == null ? void 0 : userRef.split("/").slice(1)[0];
48
+ };
49
+ const openFilter = {
50
+ name: "Open Issues",
51
+ shortName: "OPEN",
52
+ query: "resolution = Unresolved ORDER BY updated DESC"
53
+ };
54
+ const incomingFilter = {
55
+ name: "Incoming Issues",
56
+ shortName: "INCOMING",
57
+ query: "status = New ORDER BY created ASC"
58
+ };
59
+ const getDefaultFilters = (config, userRef) => {
60
+ if (!userRef) {
61
+ return [openFilter, incomingFilter];
62
+ }
63
+ const username = getUsernameFromRef(userRef);
64
+ if (!username) {
65
+ return [openFilter, incomingFilter];
66
+ }
67
+ const assignedToMeFilter = {
68
+ name: "Assigned to me",
69
+ shortName: "ME",
70
+ query: `assignee = "${username}${resolveUserEmailSuffix(
71
+ config
72
+ )}" AND resolution = Unresolved ORDER BY updated DESC`
73
+ };
74
+ return [openFilter, incomingFilter, assignedToMeFilter];
75
+ };
76
+
77
+ const getProjectInfo = async (projectKey, config) => {
78
+ const response = await fetch__default["default"](
79
+ `${resolveJiraBaseUrl(config)}project/${projectKey}`,
80
+ {
81
+ method: "GET",
82
+ headers: {
83
+ Authorization: resolveJiraToken(config),
84
+ Accept: "application/json"
85
+ }
86
+ }
87
+ );
88
+ if (response.status !== 200) {
89
+ throw Error(`${response.status}`);
90
+ }
91
+ return response.json();
92
+ };
93
+ const getFilterById = async (id, config) => {
94
+ const response = await fetch__default["default"](`${resolveJiraBaseUrl(config)}filter/${id}`, {
95
+ method: "GET",
96
+ headers: {
97
+ Authorization: resolveJiraToken(config),
98
+ Accept: "application/json"
99
+ }
100
+ });
101
+ if (response.status !== 200) {
102
+ throw Error(`${response.status}`);
103
+ }
104
+ const jsonResponse = await response.json();
105
+ return { name: jsonResponse.name, query: jsonResponse.jql };
106
+ };
107
+ const getIssuesByFilter = async (projectKey, query, config) => {
108
+ const response = await fetch__default["default"](
109
+ `${resolveJiraBaseUrl(
110
+ config
111
+ )}search?jql=project=${projectKey} AND ${query}`,
112
+ {
113
+ method: "GET",
114
+ headers: {
115
+ Authorization: resolveJiraToken(config),
116
+ Accept: "application/json"
117
+ }
118
+ }
119
+ ).then((resp) => resp.json());
120
+ return response.issues;
121
+ };
122
+ const getIssuesByComponent = async (projectKey, componentKey, config) => {
123
+ const response = await fetch__default["default"](
124
+ `${resolveJiraBaseUrl(
125
+ config
126
+ )}search?jql=project=${projectKey} AND component = "${componentKey}"`,
127
+ {
128
+ method: "GET",
129
+ headers: {
130
+ Authorization: resolveJiraToken(config),
131
+ Accept: "application/json"
132
+ }
133
+ }
134
+ ).then((resp) => resp.json());
135
+ return response.issues;
136
+ };
137
+ async function getProjectAvatar(url, config) {
138
+ const response = await fetch__default["default"](url, {
139
+ method: "GET",
140
+ headers: {
141
+ Authorization: resolveJiraToken(config)
142
+ }
143
+ });
144
+ return response;
145
+ }
146
+
147
+ const getProjectResponse = async (projectKey, config, cache) => {
148
+ let projectResponse;
149
+ projectResponse = await cache.get(projectKey);
150
+ if (projectResponse)
151
+ return projectResponse;
152
+ try {
153
+ projectResponse = await getProjectInfo(projectKey, config);
154
+ cache.set(projectKey, projectResponse);
155
+ } catch (err) {
156
+ if (err.message !== 200) {
157
+ throw Error(`${err.status}`);
158
+ }
159
+ }
160
+ return projectResponse;
161
+ };
162
+ const getFiltersFromAnnotations = async (annotations, config) => {
163
+ const filters = [];
164
+ for (const filter of annotations) {
165
+ try {
166
+ const response = await getFilterById(filter, config);
167
+ filters.push(response);
168
+ } catch (err) {
169
+ console.warn(
170
+ `${err.message} : Could not find filter with filter id ${filter}`
171
+ );
172
+ }
173
+ }
174
+ return filters;
175
+ };
176
+ const getIssuesFromFilters = async (projectKey, filters, config) => {
177
+ return await Promise.all(
178
+ filters.map(async (filter) => ({
179
+ name: filter.name,
180
+ type: "filter",
181
+ issues: await getIssuesByFilter(projectKey, filter.query, config)
182
+ }))
183
+ );
184
+ };
185
+ const getIssuesFromComponents = async (projectKey, componentAnnotations, config) => {
186
+ return await Promise.all(
187
+ componentAnnotations.map(async (componentKey) => ({
188
+ name: componentKey,
189
+ type: "component",
190
+ issues: await getIssuesByComponent(projectKey, componentKey, config)
191
+ }))
192
+ );
193
+ };
194
+
195
+ const DEFAULT_TTL = 1e3 * 60;
196
+ async function createRouter(options) {
197
+ const { logger, config, discovery, identity, tokenManager } = options;
198
+ const catalogClient$1 = new catalogClient.CatalogClient({ discoveryApi: discovery });
199
+ logger.info("Initializing Jira Dashboard backend");
200
+ const pluginCache = backendCommon.CacheManager.fromConfig(config).forPlugin("jira-dashboard");
201
+ const cache = pluginCache.getClient({ defaultTtl: DEFAULT_TTL });
202
+ const router = Router__default["default"]();
203
+ router.use(express__default["default"].json());
204
+ router.get("/health", (_, response) => {
205
+ response.json({ status: "ok" });
206
+ });
207
+ router.get(
208
+ "/dashboards/by-entity-ref/:entityRef",
209
+ async (request, response) => {
210
+ var _a, _b, _c, _d, _e, _f;
211
+ const entityRef = request.params.entityRef;
212
+ const { token } = await tokenManager.getToken();
213
+ const entity = await catalogClient$1.getEntityByRef(entityRef, { token });
214
+ if (!entity) {
215
+ logger.info(`No entity found for ${entityRef}`);
216
+ response.status(500).json({ error: `No entity found for ${entityRef}` });
217
+ return;
218
+ }
219
+ const projectKey = (_a = entity.metadata.annotations) == null ? void 0 : _a[pluginJiraDashboardCommon.PROJECT_KEY_ANNOTATION];
220
+ if (!projectKey) {
221
+ const error = `No jira.com/project-key annotation found for ${entityRef}`;
222
+ logger.info(error);
223
+ response.status(404).json(error);
224
+ return;
225
+ }
226
+ let projectResponse;
227
+ try {
228
+ projectResponse = await getProjectResponse(projectKey, config, cache);
229
+ } catch (err) {
230
+ logger.error(`Could not find Jira project ${projectKey}`);
231
+ response.status(404).json({
232
+ error: `No Jira project found with key ${projectKey}`
233
+ });
234
+ return;
235
+ }
236
+ const userIdentity = await identity.getIdentity({ request });
237
+ if (!userIdentity) {
238
+ logger.warn(`Could not find user identity`);
239
+ }
240
+ let filters = [];
241
+ const customFilterAnnotations = (_c = (_b = entity.metadata.annotations) == null ? void 0 : _b[pluginJiraDashboardCommon.FILTER_ANNOTATION]) == null ? void 0 : _c.split(",");
242
+ filters = getDefaultFilters(
243
+ config,
244
+ (_d = userIdentity == null ? void 0 : userIdentity.identity) == null ? void 0 : _d.userEntityRef
245
+ );
246
+ if (customFilterAnnotations) {
247
+ filters.push(
248
+ ...await getFiltersFromAnnotations(customFilterAnnotations, config)
249
+ );
250
+ }
251
+ let issues = await getIssuesFromFilters(projectKey, filters, config);
252
+ const componentAnnotations = (_f = (_e = entity.metadata.annotations) == null ? void 0 : _e[pluginJiraDashboardCommon.COMPONENT_ANNOTATION]) == null ? void 0 : _f.split(",");
253
+ if (componentAnnotations) {
254
+ const componentIssues = await getIssuesFromComponents(
255
+ projectKey,
256
+ componentAnnotations,
257
+ config
258
+ );
259
+ issues = issues.concat(componentIssues);
260
+ }
261
+ const jiraResponse = {
262
+ project: projectResponse,
263
+ data: issues
264
+ };
265
+ response.json(jiraResponse);
266
+ }
267
+ );
268
+ router.get("/avatar/by-entity-ref/:entityRef", async (request, response) => {
269
+ var _a;
270
+ const { entityRef } = request.params;
271
+ const { token } = await tokenManager.getToken();
272
+ const entity = await catalogClient$1.getEntityByRef(entityRef, { token });
273
+ if (!entity) {
274
+ logger.info(`No entity found for ${entityRef}`);
275
+ response.status(500).json({ error: `No entity found for ${entityRef}` });
276
+ return;
277
+ }
278
+ const projectKey = (_a = entity.metadata.annotations) == null ? void 0 : _a[pluginJiraDashboardCommon.PROJECT_KEY_ANNOTATION];
279
+ const projectResponse = await getProjectResponse(projectKey, config, cache);
280
+ if (!projectResponse) {
281
+ logger.error("Could not find project in Jira");
282
+ response.status(400).json({
283
+ error: `No Jira project found for project key ${projectKey}`
284
+ });
285
+ return;
286
+ }
287
+ const url = projectResponse.avatarUrls["48x48"];
288
+ const avatar = await getProjectAvatar(url, config);
289
+ const ps = new stream__default["default"].PassThrough();
290
+ const val = avatar.headers.get("content-type");
291
+ response.setHeader("content-type", val != null ? val : "");
292
+ stream__default["default"].pipeline(avatar.body, ps, (err) => {
293
+ if (err) {
294
+ logger.error(err);
295
+ response.sendStatus(400);
296
+ }
297
+ return;
298
+ });
299
+ ps.pipe(response);
300
+ });
301
+ router.use(backendCommon.errorHandler());
302
+ return router;
303
+ }
304
+
305
+ const jiraDashboardPlugin = backendPluginApi.createBackendPlugin({
306
+ pluginId: "jira-dashboard",
307
+ register(env) {
308
+ env.registerInit({
309
+ deps: {
310
+ logger: backendPluginApi.coreServices.logger,
311
+ config: backendPluginApi.coreServices.rootConfig,
312
+ discovery: backendPluginApi.coreServices.discovery,
313
+ identity: backendPluginApi.coreServices.identity,
314
+ tokenManager: backendPluginApi.coreServices.tokenManager,
315
+ httpRouter: backendPluginApi.coreServices.httpRouter
316
+ },
317
+ async init({
318
+ logger,
319
+ config,
320
+ discovery,
321
+ identity,
322
+ tokenManager,
323
+ httpRouter
324
+ }) {
325
+ httpRouter.use(
326
+ await createRouter({
327
+ logger: backendCommon.loggerToWinstonLogger(logger),
328
+ config,
329
+ discovery,
330
+ identity,
331
+ tokenManager
332
+ })
333
+ );
334
+ }
335
+ });
336
+ }
337
+ });
338
+
339
+ exports.createRouter = createRouter;
340
+ exports["default"] = jiraDashboardPlugin;
341
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/config.ts","../src/filters.ts","../src/api.ts","../src/service/service.ts","../src/service/router.ts","../src/plugin.ts"],"sourcesContent":["import { Config } from '@backstage/config';\n\nconst JIRA_BASE_URL_CONFIG_PATH = 'jiraDashboard.baseUrl';\nconst JIRA_TOKEN_CONFIG_PATH = 'jiraDashboard.token';\nconst JIRA_USER_CONFIG_EMAIL_SUFFIX = 'jiraDashboard.userEmailSuffix';\n\nexport function resolveJiraBaseUrl(config: Config): string {\n try {\n return config.getString(JIRA_BASE_URL_CONFIG_PATH);\n } catch (error) {\n throw new Error(`Invalid Jira baseUrl, ${error}`);\n }\n}\n\nexport function resolveJiraToken(config: Config): string {\n try {\n return config.getString(JIRA_TOKEN_CONFIG_PATH);\n } catch (error) {\n throw new Error(`Invalid Jira token, ${error}`);\n }\n}\n\nexport function resolveUserEmailSuffix(config: Config): string {\n try {\n return config.getString(JIRA_USER_CONFIG_EMAIL_SUFFIX);\n } catch (error) {\n throw new Error(`Invalid Jira user path, ${error}`);\n }\n}\n","import { Config } from '@backstage/config';\nimport { resolveUserEmailSuffix } from './config';\nimport { Filter } from '@axis-backstage/plugin-jira-dashboard-common';\n\nconst getUsernameFromRef = (userRef: string) => {\n return userRef?.split('/').slice(1)[0];\n};\n\nconst openFilter: Filter = {\n name: 'Open Issues',\n shortName: 'OPEN',\n query: 'resolution = Unresolved ORDER BY updated DESC',\n};\n\nconst incomingFilter: Filter = {\n name: 'Incoming Issues',\n shortName: 'INCOMING',\n query: 'status = New ORDER BY created ASC',\n};\n\nexport const getDefaultFilters = (\n config: Config,\n userRef?: string,\n): Filter[] => {\n if (!userRef) {\n return [openFilter, incomingFilter];\n }\n const username = getUsernameFromRef(userRef);\n\n if (!username) {\n return [openFilter, incomingFilter];\n }\n\n const assignedToMeFilter: Filter = {\n name: 'Assigned to me',\n shortName: 'ME',\n query: `assignee = \"${username}${resolveUserEmailSuffix(\n config,\n )}\" AND resolution = Unresolved ORDER BY updated DESC`,\n };\n\n return [openFilter, incomingFilter, assignedToMeFilter];\n};\n","import { Config } from '@backstage/config';\nimport fetch from 'node-fetch';\nimport {\n Filter,\n Issue,\n Project,\n} from '@axis-backstage/plugin-jira-dashboard-common';\nimport { resolveJiraBaseUrl, resolveJiraToken } from './config';\n\nexport const getProjectInfo = async (\n projectKey: string,\n config: Config,\n): Promise<Project> => {\n const response = await fetch(\n `${resolveJiraBaseUrl(config)}project/${projectKey}`,\n {\n method: 'GET',\n headers: {\n Authorization: resolveJiraToken(config),\n Accept: 'application/json',\n },\n },\n );\n if (response.status !== 200) {\n throw Error(`${response.status}`);\n }\n return response.json();\n};\n\nexport const getFilterById = async (\n id: string,\n config: Config,\n): Promise<Filter> => {\n const response = await fetch(`${resolveJiraBaseUrl(config)}filter/${id}`, {\n method: 'GET',\n headers: {\n Authorization: resolveJiraToken(config),\n Accept: 'application/json',\n },\n });\n if (response.status !== 200) {\n throw Error(`${response.status}`);\n }\n const jsonResponse = await response.json();\n return { name: jsonResponse.name, query: jsonResponse.jql } as Filter;\n};\n\nexport const getIssuesByFilter = async (\n projectKey: string,\n query: string,\n config: Config,\n): Promise<Issue[]> => {\n const response = await fetch(\n `${resolveJiraBaseUrl(\n config,\n )}search?jql=project=${projectKey} AND ${query}`,\n {\n method: 'GET',\n headers: {\n Authorization: resolveJiraToken(config),\n Accept: 'application/json',\n },\n },\n ).then(resp => resp.json());\n return response.issues;\n};\n\nexport const getIssuesByComponent = async (\n projectKey: string,\n componentKey: string,\n config: Config,\n): Promise<Issue[]> => {\n const response = await fetch(\n `${resolveJiraBaseUrl(\n config,\n )}search?jql=project=${projectKey} AND component = \"${componentKey}\"`,\n {\n method: 'GET',\n headers: {\n Authorization: resolveJiraToken(config),\n Accept: 'application/json',\n },\n },\n ).then(resp => resp.json());\n return response.issues;\n};\n\nexport async function getProjectAvatar(url: string, config: Config) {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: resolveJiraToken(config),\n },\n });\n return response;\n}\n","import { CacheClient } from '@backstage/backend-common';\nimport { Config } from '@backstage/config';\nimport {\n type Filter,\n type JiraDataResponse,\n type Project,\n} from '@axis-backstage/plugin-jira-dashboard-common';\nimport {\n getFilterById,\n getIssuesByComponent,\n getIssuesByFilter,\n getProjectInfo,\n} from '../api';\n\nexport const getProjectResponse = async (\n projectKey: string,\n config: Config,\n cache: CacheClient,\n): Promise<Project> => {\n let projectResponse: Project;\n\n projectResponse = (await cache.get(projectKey)) as Project;\n\n if (projectResponse) return projectResponse as Project;\n\n try {\n projectResponse = await getProjectInfo(projectKey, config);\n cache.set(projectKey, projectResponse);\n } catch (err: any) {\n if (err.message !== 200) {\n throw Error(`${err.status}`);\n }\n }\n return projectResponse;\n};\n\nexport const getFiltersFromAnnotations = async (\n annotations: string[],\n config: Config,\n): Promise<Filter[]> => {\n const filters: Filter[] = [];\n\n for (const filter of annotations) {\n try {\n const response = await getFilterById(filter, config);\n filters.push(response);\n } catch (err: any) {\n console.warn(\n `${err.message} : Could not find filter with filter id ${filter}`,\n );\n }\n }\n return filters;\n};\n\nexport const getIssuesFromFilters = async (\n projectKey: string,\n filters: Filter[],\n config: Config,\n): Promise<JiraDataResponse[]> => {\n return await Promise.all(\n filters.map(async filter => ({\n name: filter.name,\n type: 'filter',\n issues: await getIssuesByFilter(projectKey, filter.query, config),\n })),\n );\n};\n\nexport const getIssuesFromComponents = async (\n projectKey: string,\n componentAnnotations: string[],\n config: Config,\n): Promise<JiraDataResponse[]> => {\n return await Promise.all(\n componentAnnotations.map(async componentKey => ({\n name: componentKey,\n type: 'component',\n issues: await getIssuesByComponent(projectKey, componentKey, config),\n })),\n );\n};\n","import {\n CacheManager,\n TokenManager,\n errorHandler,\n} from '@backstage/backend-common';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { Config } from '@backstage/config';\nimport { Logger } from 'winston';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { DiscoveryApi } from '@backstage/plugin-permission-common';\nimport { IdentityApi } from '@backstage/plugin-auth-node';\n\nimport { getDefaultFilters } from '../filters';\nimport {\n COMPONENT_ANNOTATION,\n FILTER_ANNOTATION,\n type Filter,\n type JiraResponse,\n PROJECT_KEY_ANNOTATION,\n type Project,\n} from '@axis-backstage/plugin-jira-dashboard-common';\nimport stream from 'stream';\nimport { getProjectAvatar } from '../api';\nimport {\n getProjectResponse,\n getFiltersFromAnnotations,\n getIssuesFromFilters,\n getIssuesFromComponents,\n} from './service';\n\n/**\n * Constructs a jira dashboard router.\n * @public\n */\nexport interface RouterOptions {\n /**\n * Implementation of Winston logger\n */\n logger: Logger;\n\n /**\n * Backstage config object\n */\n config: Config;\n\n /**\n * Backstage discovery api instance\n */\n discovery: DiscoveryApi;\n\n /**\n * Backstage identity api instance\n */\n identity: IdentityApi;\n\n /**\n * Backstage token manager instance\n */\n tokenManager: TokenManager;\n}\n\nconst DEFAULT_TTL = 1000 * 60;\n\n/**\n * Constructs a jira dashboard router.\n *\n * @public\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { logger, config, discovery, identity, tokenManager } = options;\n const catalogClient = new CatalogClient({ discoveryApi: discovery });\n logger.info('Initializing Jira Dashboard backend');\n\n const pluginCache =\n CacheManager.fromConfig(config).forPlugin('jira-dashboard');\n const cache = pluginCache.getClient({ defaultTtl: DEFAULT_TTL });\n\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n response.json({ status: 'ok' });\n });\n\n router.get(\n '/dashboards/by-entity-ref/:entityRef',\n async (request, response) => {\n const entityRef = request.params.entityRef;\n const { token } = await tokenManager.getToken();\n const entity = await catalogClient.getEntityByRef(entityRef, { token });\n\n if (!entity) {\n logger.info(`No entity found for ${entityRef}`);\n response\n .status(500)\n .json({ error: `No entity found for ${entityRef}` });\n return;\n }\n\n const projectKey = entity.metadata.annotations?.[PROJECT_KEY_ANNOTATION]!;\n\n if (!projectKey) {\n const error = `No jira.com/project-key annotation found for ${entityRef}`;\n logger.info(error);\n response.status(404).json(error);\n return;\n }\n\n let projectResponse;\n\n try {\n projectResponse = await getProjectResponse(projectKey, config, cache);\n } catch (err) {\n logger.error(`Could not find Jira project ${projectKey}`);\n response.status(404).json({\n error: `No Jira project found with key ${projectKey}`,\n });\n return;\n }\n\n const userIdentity = await identity.getIdentity({ request: request });\n\n if (!userIdentity) {\n logger.warn(`Could not find user identity`);\n }\n\n let filters: Filter[] = [];\n\n const customFilterAnnotations =\n entity.metadata.annotations?.[FILTER_ANNOTATION]?.split(',')!;\n\n filters = getDefaultFilters(\n config,\n userIdentity?.identity?.userEntityRef,\n );\n\n if (customFilterAnnotations) {\n filters.push(\n ...(await getFiltersFromAnnotations(customFilterAnnotations, config)),\n );\n }\n\n let issues = await getIssuesFromFilters(projectKey, filters, config);\n\n const componentAnnotations =\n entity.metadata.annotations?.[COMPONENT_ANNOTATION]?.split(',')!;\n\n if (componentAnnotations) {\n const componentIssues = await getIssuesFromComponents(\n projectKey,\n componentAnnotations,\n config,\n );\n issues = issues.concat(componentIssues);\n }\n\n const jiraResponse: JiraResponse = {\n project: projectResponse as Project,\n data: issues,\n };\n response.json(jiraResponse);\n },\n );\n\n router.get('/avatar/by-entity-ref/:entityRef', async (request, response) => {\n const { entityRef } = request.params;\n const { token } = await tokenManager.getToken();\n const entity = await catalogClient.getEntityByRef(entityRef, { token });\n\n if (!entity) {\n logger.info(`No entity found for ${entityRef}`);\n response.status(500).json({ error: `No entity found for ${entityRef}` });\n return;\n }\n\n const projectKey = entity.metadata.annotations?.[PROJECT_KEY_ANNOTATION]!;\n\n const projectResponse = await getProjectResponse(projectKey, config, cache);\n\n if (!projectResponse) {\n logger.error('Could not find project in Jira');\n response.status(400).json({\n error: `No Jira project found for project key ${projectKey}`,\n });\n return;\n }\n\n const url = projectResponse.avatarUrls['48x48'];\n\n const avatar = await getProjectAvatar(url, config);\n\n const ps = new stream.PassThrough();\n const val = avatar.headers.get('content-type');\n\n response.setHeader('content-type', val ?? '');\n stream.pipeline(avatar.body, ps, err => {\n if (err) {\n logger.error(err);\n response.sendStatus(400);\n }\n return;\n });\n ps.pipe(response);\n });\n router.use(errorHandler());\n return router;\n}\n","import { loggerToWinstonLogger } from '@backstage/backend-common';\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\n\n/**\n * The Jira Dashboard backend plugin.\n *\n * @public\n */\nexport const jiraDashboardPlugin = createBackendPlugin({\n pluginId: 'jira-dashboard',\n register(env) {\n env.registerInit({\n deps: {\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n discovery: coreServices.discovery,\n identity: coreServices.identity,\n tokenManager: coreServices.tokenManager,\n httpRouter: coreServices.httpRouter,\n },\n async init({\n logger,\n config,\n discovery,\n identity,\n tokenManager,\n httpRouter,\n }) {\n httpRouter.use(\n await createRouter({\n logger: loggerToWinstonLogger(logger),\n config,\n discovery,\n identity,\n tokenManager,\n }),\n );\n },\n });\n },\n});\n"],"names":["fetch","catalogClient","CatalogClient","CacheManager","Router","express","PROJECT_KEY_ANNOTATION","FILTER_ANNOTATION","COMPONENT_ANNOTATION","stream","errorHandler","createBackendPlugin","coreServices","loggerToWinstonLogger"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { TokenManager } from '@backstage/backend-common';
2
+ import express from 'express';
3
+ import { Config } from '@backstage/config';
4
+ import { Logger } from 'winston';
5
+ import { DiscoveryApi } from '@backstage/plugin-permission-common';
6
+ import { IdentityApi } from '@backstage/plugin-auth-node';
7
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
8
+
9
+ /**
10
+ * Constructs a jira dashboard router.
11
+ * @public
12
+ */
13
+ interface RouterOptions {
14
+ /**
15
+ * Implementation of Winston logger
16
+ */
17
+ logger: Logger;
18
+ /**
19
+ * Backstage config object
20
+ */
21
+ config: Config;
22
+ /**
23
+ * Backstage discovery api instance
24
+ */
25
+ discovery: DiscoveryApi;
26
+ /**
27
+ * Backstage identity api instance
28
+ */
29
+ identity: IdentityApi;
30
+ /**
31
+ * Backstage token manager instance
32
+ */
33
+ tokenManager: TokenManager;
34
+ }
35
+ /**
36
+ * Constructs a jira dashboard router.
37
+ *
38
+ * @public
39
+ */
40
+ declare function createRouter(options: RouterOptions): Promise<express.Router>;
41
+
42
+ /**
43
+ * The Jira Dashboard backend plugin.
44
+ *
45
+ * @public
46
+ */
47
+ declare const jiraDashboardPlugin: () => _backstage_backend_plugin_api.BackendFeature;
48
+
49
+ export { RouterOptions, createRouter, jiraDashboardPlugin as default };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@axis-backstage/plugin-jira-dashboard-backend",
3
+ "version": "0.1.0",
4
+ "main": "dist/index.cjs.js",
5
+ "types": "dist/index.d.ts",
6
+ "license": "Apache-2.0",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "main": "dist/index.cjs.js",
10
+ "types": "dist/index.d.ts"
11
+ },
12
+ "backstage": {
13
+ "role": "backend-plugin"
14
+ },
15
+ "scripts": {
16
+ "start": "backstage-cli package start",
17
+ "build": "backstage-cli package build",
18
+ "lint": "backstage-cli package lint",
19
+ "test": "backstage-cli package test",
20
+ "clean": "backstage-cli package clean",
21
+ "prepack": "backstage-cli package prepack",
22
+ "postpack": "backstage-cli package postpack"
23
+ },
24
+ "dependencies": {
25
+ "@axis-backstage/plugin-jira-dashboard-common": "^0.1.0",
26
+ "@backstage/backend-common": "^0.19.8",
27
+ "@backstage/backend-plugin-api": "^0.6.6",
28
+ "@backstage/catalog-client": "^1.4.5",
29
+ "@backstage/config": "^1.1.1",
30
+ "@backstage/plugin-auth-node": "^0.4.0",
31
+ "@backstage/plugin-permission-common": "^0.7.9",
32
+ "@types/express": "*",
33
+ "express": "^4.17.1",
34
+ "express-promise-router": "^4.1.0",
35
+ "node-fetch": "^2.6.7",
36
+ "winston": "^3.2.1",
37
+ "yn": "^4.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@backstage/cli": "^0.23.0",
41
+ "@types/supertest": "^2.0.12",
42
+ "msw": "^1.0.0",
43
+ "supertest": "^6.2.4"
44
+ },
45
+ "files": [
46
+ "dist"
47
+ ]
48
+ }