@axis-backstage/plugin-jira-dashboard-backend 2.7.0 → 3.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 +26 -43
- package/config.d.ts +55 -22
- package/dist/index.cjs.js +256 -178
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +10 -50
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -43,6 +43,32 @@ jiraDashboard:
|
|
|
43
43
|
annotationPrefix: jira
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
### Multiple Jira instances
|
|
47
|
+
|
|
48
|
+
In case multiple Jira instances are being used, the configuration can be written on the form:
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
jiraDashboard:
|
|
52
|
+
annotationPrefix: ${JIRA_ANNOTATION_PREFIX} # Optional
|
|
53
|
+
instances:
|
|
54
|
+
- name: default
|
|
55
|
+
token: ${JIRA_TOKEN}
|
|
56
|
+
baseUrl: ${JIRA_BASE_URL}
|
|
57
|
+
userEmailSuffix: ${JIRA_EMAIL_SUFFIX} # Optional
|
|
58
|
+
- name: separate-jira-instance
|
|
59
|
+
token: ${JIRA_TOKEN_SEPARATE}
|
|
60
|
+
baseUrl: ${JIRA_BASE_URL_SEPARATE}
|
|
61
|
+
userEmailSuffix: ${JIRA_EMAIL_SUFFIX_SEPARATE} # Optional
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
In entity yamls that don't specify an instance, the one called `"default"` will be used. To specify another instace, prefix the project key with `instance-name/` such as:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
metadata:
|
|
68
|
+
annotations:
|
|
69
|
+
jira.com/project-key: separate-jira-instance/my-project-key
|
|
70
|
+
```
|
|
71
|
+
|
|
46
72
|
#### Authentication examples and trouble shooting
|
|
47
73
|
|
|
48
74
|
Either "Basic Auth" or "Personal Acccess Tokens" can be used.
|
|
@@ -96,49 +122,6 @@ jiraDashboard:
|
|
|
96
122
|
|
|
97
123
|
### Integrating
|
|
98
124
|
|
|
99
|
-
Here's how to get the backend plugin up and running:
|
|
100
|
-
|
|
101
|
-
1. Create a new file named `packages/backend/src/plugins/jiraDashboard.ts`, and add the following to it:
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
import { createRouter } from '@axis-backstage/plugin-jira-dashboard-backend';
|
|
105
|
-
import { Router } from 'express';
|
|
106
|
-
import { PluginEnvironment } from '../types';
|
|
107
|
-
|
|
108
|
-
export default async function createPlugin(
|
|
109
|
-
env: PluginEnvironment,
|
|
110
|
-
): Promise<Router> {
|
|
111
|
-
return await createRouter({
|
|
112
|
-
logger: env.logger,
|
|
113
|
-
config: env.config,
|
|
114
|
-
discovery: env.discovery,
|
|
115
|
-
identity: env.identity,
|
|
116
|
-
tokenManager: env.tokenManager,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
2. Wire this into the overall backend router by adding the following to `packages/backend/src/index.ts`:
|
|
122
|
-
|
|
123
|
-
```ts
|
|
124
|
-
import jiraDashboard from './plugins/jiraDashboard';
|
|
125
|
-
...
|
|
126
|
-
|
|
127
|
-
async function main() {
|
|
128
|
-
// Add this line under the other lines that follow the useHotMemoize pattern
|
|
129
|
-
const jiraDashboardEnv = useHotMemoize(module, () => createEnv('jira-dashboard'),
|
|
130
|
-
|
|
131
|
-
// Add this under the lines that add their routers to apiRouter
|
|
132
|
-
apiRouter.use('/jira-dashboard', await jiraDashboard(jiraDashboardEnv));
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
3. Now run `yarn start-backend` from the repo root.
|
|
137
|
-
|
|
138
|
-
4. In another terminal, run the command: `curl localhost:7007/api/jira-dashboard/health`. The request should return `{"status":"ok"}`.
|
|
139
|
-
|
|
140
|
-
### New Backend System
|
|
141
|
-
|
|
142
125
|
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:
|
|
143
126
|
|
|
144
127
|
In your `packages/backend/src/index.ts` make the following changes:
|
package/config.d.ts
CHANGED
|
@@ -1,25 +1,58 @@
|
|
|
1
1
|
export interface Config {
|
|
2
2
|
/** Configuration options for the Jira Dashboard plugin */
|
|
3
|
-
jiraDashboard:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
3
|
+
jiraDashboard:
|
|
4
|
+
| {
|
|
5
|
+
/**
|
|
6
|
+
* 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/
|
|
7
|
+
* @visibility secret
|
|
8
|
+
*/
|
|
9
|
+
token: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The base url for Jira in your company, including the API version. For instance: https://jira.se.your-company.com/rest/api/2/'
|
|
13
|
+
*/
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Optional email suffix used for retrieving a specific Jira user in a company. For instance: @your-company.com. If not provided, the user entity profile email is used instead.
|
|
18
|
+
*/
|
|
19
|
+
userEmailSuffix?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Optional annotation prefix for retrieving a custom annotation. Defaut value is jira.com
|
|
23
|
+
*/
|
|
24
|
+
annotationPrefix?: string;
|
|
25
|
+
|
|
26
|
+
// Type helper
|
|
27
|
+
instances?: never;
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
/**
|
|
31
|
+
* Optional annotation prefix for retrieving a custom annotation. Defaut value is jira.com
|
|
32
|
+
*/
|
|
33
|
+
annotationPrefix?: string;
|
|
34
|
+
|
|
35
|
+
instances: {
|
|
36
|
+
/**
|
|
37
|
+
* Instance name. This is used in entity annotations to refer to the right instance. In entity annotations, this defaults to "default".
|
|
38
|
+
*/
|
|
39
|
+
name: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 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/
|
|
43
|
+
* @visibility secret
|
|
44
|
+
*/
|
|
45
|
+
token: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The base url for Jira in your company, including the API version. For instance: https://jira.se.your-company.com/rest/api/2/'
|
|
49
|
+
*/
|
|
50
|
+
baseUrl: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional email suffix used for retrieving a specific Jira user in a company. For instance: @your-company.com. If not provided, the user entity profile email is used instead.
|
|
54
|
+
*/
|
|
55
|
+
userEmailSuffix?: string;
|
|
56
|
+
}[];
|
|
57
|
+
};
|
|
25
58
|
}
|
package/dist/index.cjs.js
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
6
|
+
var express = require('express');
|
|
7
|
+
var Router = require('express-promise-router');
|
|
8
|
+
var stream = require('stream');
|
|
6
9
|
var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
|
|
7
10
|
var cache = require('@backstage/backend-defaults/cache');
|
|
8
11
|
var catalogModel = require('@backstage/catalog-model');
|
|
9
|
-
var express = require('express');
|
|
10
|
-
var Router = require('express-promise-router');
|
|
11
12
|
var catalogClient = require('@backstage/catalog-client');
|
|
12
|
-
var stream = require('stream');
|
|
13
|
-
var fetch = require('node-fetch');
|
|
14
13
|
var pluginJiraDashboardCommon = require('@axis-backstage/plugin-jira-dashboard-common');
|
|
15
|
-
var
|
|
14
|
+
var fetch = require('node-fetch');
|
|
15
|
+
var errors = require('@backstage/errors');
|
|
16
16
|
|
|
17
17
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
18
18
|
|
|
@@ -21,60 +21,36 @@ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
|
21
21
|
var stream__default = /*#__PURE__*/_interopDefaultCompat(stream);
|
|
22
22
|
var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} catch (error) {
|
|
32
|
-
throw new Error(`Invalid Jira baseUrl, ${error}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function resolveJiraToken(config) {
|
|
36
|
-
try {
|
|
37
|
-
return config.getString(JIRA_TOKEN_CONFIG_PATH);
|
|
38
|
-
} catch (error) {
|
|
39
|
-
throw new Error(`Invalid Jira token, ${error}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function resolveUserEmailSuffix(config) {
|
|
43
|
-
return config.getOptionalString(JIRA_USER_CONFIG_EMAIL_SUFFIX);
|
|
44
|
-
}
|
|
45
|
-
function resolveAnnotationPrefix(config) {
|
|
46
|
-
const annotationPrefix = config.getOptionalString(JIRA_ANNOTATION);
|
|
47
|
-
return annotationPrefix ?? "jira.com";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const openFilter = {
|
|
51
|
-
name: "Open Issues",
|
|
52
|
-
shortName: "OPEN",
|
|
53
|
-
query: "resolution = Unresolved ORDER BY updated DESC"
|
|
54
|
-
};
|
|
55
|
-
const getIncomingFilter = (incomingStatus) => ({
|
|
56
|
-
name: "Incoming Issues",
|
|
57
|
-
shortName: "INCOMING",
|
|
58
|
-
query: `status = '${incomingStatus}' ORDER BY created ASC`
|
|
59
|
-
});
|
|
60
|
-
const getAssigneUser = (config, userEntity) => {
|
|
61
|
-
const emailSuffixConfig = resolveUserEmailSuffix(config);
|
|
62
|
-
return emailSuffixConfig ? `${userEntity.metadata.name}${emailSuffixConfig}` : userEntity.spec?.profile?.email || userEntity.metadata.name;
|
|
63
|
-
};
|
|
64
|
-
const getAssignedToMeFilter = (userEntity, config) => {
|
|
65
|
-
const email = getAssigneUser(config, userEntity);
|
|
24
|
+
const getAnnotations = (config) => {
|
|
25
|
+
const prefix = config.annotationPrefix;
|
|
26
|
+
const projectKeyAnnotation = `${prefix}/${pluginJiraDashboardCommon.PROJECT_KEY_NAME}`;
|
|
27
|
+
const componentsAnnotation = `${prefix}/${pluginJiraDashboardCommon.COMPONENTS_NAME}`;
|
|
28
|
+
const filtersAnnotation = `${prefix}/${pluginJiraDashboardCommon.FILTERS_NAME}`;
|
|
29
|
+
const incomingIssuesAnnotation = `${prefix}/${pluginJiraDashboardCommon.INCOMING_ISSUES_STATUS}`;
|
|
30
|
+
const componentRoadieAnnotation = `${prefix}/component`;
|
|
66
31
|
return {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
32
|
+
projectKeyAnnotation,
|
|
33
|
+
componentsAnnotation,
|
|
34
|
+
filtersAnnotation,
|
|
35
|
+
incomingIssuesAnnotation,
|
|
36
|
+
componentRoadieAnnotation
|
|
70
37
|
};
|
|
71
38
|
};
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
39
|
+
function splitProjectKey(config, fullProjectKey) {
|
|
40
|
+
const [instance, projectKey] = fullProjectKey.split("/");
|
|
41
|
+
if (!projectKey) {
|
|
42
|
+
return {
|
|
43
|
+
instance: config.getInstance(),
|
|
44
|
+
fullProjectKey,
|
|
45
|
+
projectKey: instance
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
instance: config.getInstance(instance),
|
|
50
|
+
fullProjectKey,
|
|
51
|
+
projectKey
|
|
52
|
+
};
|
|
53
|
+
}
|
|
78
54
|
|
|
79
55
|
const jqlQueryBuilder = ({
|
|
80
56
|
project,
|
|
@@ -101,17 +77,15 @@ const jqlQueryBuilder = ({
|
|
|
101
77
|
return jql;
|
|
102
78
|
};
|
|
103
79
|
|
|
104
|
-
const getProjectInfo = async (
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
Accept: "application/json"
|
|
112
|
-
}
|
|
80
|
+
const getProjectInfo = async (project) => {
|
|
81
|
+
const { projectKey, instance } = project;
|
|
82
|
+
const response = await fetch__default.default(`${instance.baseUrl}project/${projectKey}`, {
|
|
83
|
+
method: "GET",
|
|
84
|
+
headers: {
|
|
85
|
+
Authorization: instance.token,
|
|
86
|
+
Accept: "application/json"
|
|
113
87
|
}
|
|
114
|
-
);
|
|
88
|
+
});
|
|
115
89
|
if (response.status !== 200) {
|
|
116
90
|
throw Error(
|
|
117
91
|
`Request failed with status code ${response.status}: ${response.statusText}`
|
|
@@ -119,11 +93,11 @@ const getProjectInfo = async (projectKey, config) => {
|
|
|
119
93
|
}
|
|
120
94
|
return response.json();
|
|
121
95
|
};
|
|
122
|
-
const getFilterById = async (id,
|
|
123
|
-
const response = await fetch__default.default(`${
|
|
96
|
+
const getFilterById = async (id, instance) => {
|
|
97
|
+
const response = await fetch__default.default(`${instance.baseUrl}filter/${id}`, {
|
|
124
98
|
method: "GET",
|
|
125
99
|
headers: {
|
|
126
|
-
Authorization:
|
|
100
|
+
Authorization: instance.token,
|
|
127
101
|
Accept: "application/json"
|
|
128
102
|
}
|
|
129
103
|
});
|
|
@@ -133,70 +107,67 @@ const getFilterById = async (id, config) => {
|
|
|
133
107
|
const jsonResponse = await response.json();
|
|
134
108
|
return { name: jsonResponse.name, query: jsonResponse.jql };
|
|
135
109
|
};
|
|
136
|
-
const getIssuesByFilter = async (
|
|
110
|
+
const getIssuesByFilter = async (project, components, query) => {
|
|
111
|
+
const { projectKey, instance } = project;
|
|
137
112
|
const jql = jqlQueryBuilder({ project: projectKey, components, query });
|
|
138
|
-
const response = await fetch__default.default(
|
|
139
|
-
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
Authorization: resolveJiraToken(config),
|
|
144
|
-
Accept: "application/json"
|
|
145
|
-
}
|
|
113
|
+
const response = await fetch__default.default(`${instance.baseUrl}search?jql=${jql}`, {
|
|
114
|
+
method: "GET",
|
|
115
|
+
headers: {
|
|
116
|
+
Authorization: instance.token,
|
|
117
|
+
Accept: "application/json"
|
|
146
118
|
}
|
|
147
|
-
).then((resp) => resp.json());
|
|
119
|
+
}).then((resp) => resp.json());
|
|
148
120
|
return response.issues;
|
|
149
121
|
};
|
|
150
|
-
const searchJira = async (
|
|
151
|
-
const response = await fetch__default.default(`${
|
|
122
|
+
const searchJira = async (instance, jqlQuery, options) => {
|
|
123
|
+
const response = await fetch__default.default(`${instance.baseUrl}search`, {
|
|
152
124
|
method: "POST",
|
|
153
125
|
body: JSON.stringify({ jql: jqlQuery, ...options }),
|
|
154
126
|
headers: {
|
|
155
|
-
Authorization:
|
|
127
|
+
Authorization: instance.token,
|
|
156
128
|
Accept: "application/json",
|
|
157
129
|
"Content-Type": "application/json"
|
|
158
130
|
}
|
|
159
131
|
}).then((resp) => resp.json());
|
|
160
132
|
return response.issues;
|
|
161
133
|
};
|
|
162
|
-
const getIssuesByComponent = async (
|
|
134
|
+
const getIssuesByComponent = async (project, componentKey) => {
|
|
135
|
+
const { projectKey, instance } = project;
|
|
163
136
|
const jql = jqlQueryBuilder({
|
|
164
137
|
project: projectKey,
|
|
165
138
|
components: [componentKey]
|
|
166
139
|
});
|
|
167
|
-
const response = await fetch__default.default(
|
|
168
|
-
|
|
169
|
-
{
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Authorization: resolveJiraToken(config),
|
|
173
|
-
Accept: "application/json"
|
|
174
|
-
}
|
|
140
|
+
const response = await fetch__default.default(`${instance.baseUrl}search?jql=${jql}`, {
|
|
141
|
+
method: "GET",
|
|
142
|
+
headers: {
|
|
143
|
+
Authorization: instance.token,
|
|
144
|
+
Accept: "application/json"
|
|
175
145
|
}
|
|
176
|
-
).then((resp) => resp.json());
|
|
146
|
+
}).then((resp) => resp.json());
|
|
177
147
|
return response.issues;
|
|
178
148
|
};
|
|
179
|
-
async function getProjectAvatar(url,
|
|
180
|
-
|
|
149
|
+
async function getProjectAvatar(url, instance) {
|
|
150
|
+
return fetch__default.default(url, {
|
|
181
151
|
method: "GET",
|
|
182
152
|
headers: {
|
|
183
|
-
Authorization:
|
|
153
|
+
Authorization: instance.token
|
|
184
154
|
}
|
|
185
155
|
});
|
|
186
|
-
return response;
|
|
187
156
|
}
|
|
188
157
|
|
|
189
|
-
const getProjectResponse = async (
|
|
158
|
+
const getProjectResponse = async (project, cache) => {
|
|
190
159
|
let projectResponse;
|
|
191
|
-
projectResponse = await cache.get(
|
|
192
|
-
if (projectResponse)
|
|
160
|
+
projectResponse = await cache.get(project.fullProjectKey);
|
|
161
|
+
if (projectResponse) {
|
|
162
|
+
return projectResponse;
|
|
163
|
+
}
|
|
193
164
|
try {
|
|
194
|
-
projectResponse = await getProjectInfo(
|
|
195
|
-
cache.set(
|
|
165
|
+
projectResponse = await getProjectInfo(project);
|
|
166
|
+
cache.set(project.fullProjectKey, projectResponse);
|
|
196
167
|
} catch (err) {
|
|
197
168
|
if (err.message !== 200) {
|
|
198
169
|
throw Error(
|
|
199
|
-
`Failed to get project info for project key ${
|
|
170
|
+
`Failed to get project info for project key ${project.fullProjectKey} with error: ${err.message}`
|
|
200
171
|
);
|
|
201
172
|
}
|
|
202
173
|
}
|
|
@@ -205,7 +176,9 @@ const getProjectResponse = async (projectKey, config, cache) => {
|
|
|
205
176
|
const getJqlResponse = async (jql, config, cache, searchOptions) => {
|
|
206
177
|
let issuesResponse;
|
|
207
178
|
issuesResponse = await cache.get(jql);
|
|
208
|
-
if (issuesResponse)
|
|
179
|
+
if (issuesResponse) {
|
|
180
|
+
return issuesResponse;
|
|
181
|
+
}
|
|
209
182
|
try {
|
|
210
183
|
issuesResponse = await searchJira(config, jql, searchOptions);
|
|
211
184
|
cache.set(jql, issuesResponse);
|
|
@@ -247,67 +220,76 @@ const getFiltersFromAnnotations = async (annotations, config) => {
|
|
|
247
220
|
}
|
|
248
221
|
return filters;
|
|
249
222
|
};
|
|
250
|
-
const getIssuesFromFilters = async (
|
|
223
|
+
const getIssuesFromFilters = async (project, components, filters) => {
|
|
251
224
|
return await Promise.all(
|
|
252
225
|
filters.map(async (filter) => ({
|
|
253
226
|
name: filter.name,
|
|
254
227
|
query: jqlQueryBuilder({
|
|
255
|
-
project: projectKey,
|
|
228
|
+
project: project.projectKey,
|
|
256
229
|
components,
|
|
257
230
|
query: filter.query
|
|
258
231
|
}),
|
|
259
232
|
type: "filter",
|
|
260
|
-
issues: await getIssuesByFilter(
|
|
261
|
-
projectKey,
|
|
262
|
-
components,
|
|
263
|
-
filter.query,
|
|
264
|
-
config
|
|
265
|
-
)
|
|
233
|
+
issues: await getIssuesByFilter(project, components, filter.query)
|
|
266
234
|
}))
|
|
267
235
|
);
|
|
268
236
|
};
|
|
269
|
-
const getIssuesFromComponents = async (
|
|
237
|
+
const getIssuesFromComponents = async (project, componentAnnotations) => {
|
|
270
238
|
return await Promise.all(
|
|
271
239
|
componentAnnotations.map(async (componentKey) => ({
|
|
272
240
|
name: componentKey,
|
|
273
241
|
query: jqlQueryBuilder({
|
|
274
|
-
project: projectKey,
|
|
242
|
+
project: project.projectKey,
|
|
275
243
|
components: [componentKey]
|
|
276
244
|
}),
|
|
277
245
|
type: "component",
|
|
278
|
-
issues: await getIssuesByComponent(
|
|
246
|
+
issues: await getIssuesByComponent(project, componentKey)
|
|
279
247
|
}))
|
|
280
248
|
);
|
|
281
249
|
};
|
|
282
250
|
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
251
|
+
const DEFAULT_TTL = 1e3 * 60;
|
|
252
|
+
const DEFAULT_MAX_RESULTS_USER_ISSUES = 10;
|
|
253
|
+
|
|
254
|
+
const openFilter = {
|
|
255
|
+
name: "Open Issues",
|
|
256
|
+
shortName: "OPEN",
|
|
257
|
+
query: "resolution = Unresolved ORDER BY updated DESC"
|
|
258
|
+
};
|
|
259
|
+
const getIncomingFilter = (incomingStatus) => ({
|
|
260
|
+
name: "Incoming Issues",
|
|
261
|
+
shortName: "INCOMING",
|
|
262
|
+
query: `status = '${incomingStatus}' ORDER BY created ASC`
|
|
263
|
+
});
|
|
264
|
+
const getAssigneUser = (instance, userEntity) => {
|
|
265
|
+
const emailSuffixConfig = instance.userEmailSuffix;
|
|
266
|
+
return emailSuffixConfig ? `${userEntity.metadata.name}${emailSuffixConfig}` : userEntity.spec?.profile?.email || userEntity.metadata.name;
|
|
267
|
+
};
|
|
268
|
+
const getAssignedToMeFilter = (userEntity, instance) => {
|
|
269
|
+
const email = getAssigneUser(instance, userEntity);
|
|
290
270
|
return {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
incomingIssuesAnnotation,
|
|
295
|
-
componentRoadieAnnotation
|
|
271
|
+
name: "Assigned to me",
|
|
272
|
+
shortName: "ME",
|
|
273
|
+
query: `assignee = "${email}" AND resolution = Unresolved ORDER BY updated DESC`
|
|
296
274
|
};
|
|
297
275
|
};
|
|
276
|
+
const getDefaultFiltersForUser = (instance, userEntity, incomingStatus) => {
|
|
277
|
+
const incomingFilter = getIncomingFilter(incomingStatus ?? "New");
|
|
278
|
+
if (!userEntity) return [openFilter, incomingFilter];
|
|
279
|
+
const assigneeToMeFilter = getAssignedToMeFilter(userEntity, instance);
|
|
280
|
+
return [openFilter, incomingFilter, assigneeToMeFilter];
|
|
281
|
+
};
|
|
298
282
|
|
|
299
|
-
const DEFAULT_TTL = 1e3 * 60;
|
|
300
|
-
const DEFAULT_MAX_RESULTS_USER_ISSUES = 10;
|
|
301
283
|
async function createRouter(options) {
|
|
302
|
-
const { auth, httpAuth } =
|
|
303
|
-
const { logger, config, discovery, userInfo } = options;
|
|
284
|
+
const { auth, logger, rootConfig, config, discovery, httpAuth, userInfo } = options;
|
|
304
285
|
const catalogClient$1 = new catalogClient.CatalogClient({ discoveryApi: discovery });
|
|
305
|
-
const pluginCache = cache.CacheManager.fromConfig(
|
|
286
|
+
const pluginCache = cache.CacheManager.fromConfig(rootConfig).forPlugin("jira-dashboard");
|
|
306
287
|
const cache$1 = pluginCache.getClient({ defaultTtl: DEFAULT_TTL });
|
|
307
288
|
logger.info("Initializing Jira Dashboard backend");
|
|
308
289
|
const router = Router__default.default();
|
|
309
290
|
router.use(express__default.default.json());
|
|
310
291
|
router.get("/health", (_, response) => {
|
|
292
|
+
logger.info("PONG!");
|
|
311
293
|
response.json({ status: "ok" });
|
|
312
294
|
});
|
|
313
295
|
router.get(
|
|
@@ -332,26 +314,25 @@ async function createRouter(options) {
|
|
|
332
314
|
response.status(500).json({ error: `No entity found for ${entityRef}` });
|
|
333
315
|
return;
|
|
334
316
|
}
|
|
335
|
-
const
|
|
336
|
-
if (!
|
|
317
|
+
const fullProjectKeys = entity.metadata.annotations?.[projectKeyAnnotation]?.split(",");
|
|
318
|
+
if (!fullProjectKeys) {
|
|
337
319
|
const error = `No jira.com/project-key annotation found for ${entityRef}`;
|
|
338
320
|
logger.info(error);
|
|
339
321
|
response.status(404).json(error);
|
|
340
322
|
return;
|
|
341
323
|
}
|
|
324
|
+
const projects = fullProjectKeys.map(
|
|
325
|
+
(fullProjectKey) => splitProjectKey(config, fullProjectKey)
|
|
326
|
+
);
|
|
342
327
|
let projectResponse;
|
|
343
328
|
try {
|
|
344
|
-
projectResponse = await getProjectResponse(
|
|
345
|
-
projectKey[0],
|
|
346
|
-
config,
|
|
347
|
-
cache$1
|
|
348
|
-
);
|
|
329
|
+
projectResponse = await getProjectResponse(projects[0], cache$1);
|
|
349
330
|
} catch (err) {
|
|
350
331
|
logger.error(
|
|
351
|
-
`Could not find Jira project ${
|
|
332
|
+
`Could not find Jira project ${projects[0].fullProjectKey}: ${err.message}`
|
|
352
333
|
);
|
|
353
334
|
response.status(404).json({
|
|
354
|
-
error: `No Jira project found with key ${
|
|
335
|
+
error: `No Jira project found with key ${projects[0].projectKey}`
|
|
355
336
|
});
|
|
356
337
|
return;
|
|
357
338
|
}
|
|
@@ -369,28 +350,29 @@ async function createRouter(options) {
|
|
|
369
350
|
}
|
|
370
351
|
let filters = [];
|
|
371
352
|
const incomingStatus = entity.metadata.annotations?.[incomingIssuesAnnotation];
|
|
372
|
-
filters = getDefaultFiltersForUser(
|
|
353
|
+
filters = getDefaultFiltersForUser(
|
|
354
|
+
projects[0].instance,
|
|
355
|
+
userEntity,
|
|
356
|
+
incomingStatus
|
|
357
|
+
);
|
|
373
358
|
const customFilterAnnotations = entity.metadata.annotations?.[filtersAnnotation]?.split(",");
|
|
374
359
|
if (customFilterAnnotations) {
|
|
375
360
|
filters.push(
|
|
376
|
-
...await getFiltersFromAnnotations(
|
|
361
|
+
...await getFiltersFromAnnotations(
|
|
362
|
+
customFilterAnnotations,
|
|
363
|
+
projects[0].instance
|
|
364
|
+
)
|
|
377
365
|
);
|
|
378
366
|
}
|
|
379
367
|
let components = entity.metadata.annotations?.[componentsAnnotation]?.split(",") ?? [];
|
|
380
|
-
let issues = await getIssuesFromFilters(
|
|
381
|
-
projectKey[0],
|
|
382
|
-
components,
|
|
383
|
-
filters,
|
|
384
|
-
config
|
|
385
|
-
);
|
|
368
|
+
let issues = await getIssuesFromFilters(projects[0], components, filters);
|
|
386
369
|
components = components.concat(
|
|
387
370
|
entity.metadata.annotations?.[componentRoadieAnnotation]?.split(",") ?? []
|
|
388
371
|
);
|
|
389
372
|
if (components) {
|
|
390
373
|
const componentIssues = await getIssuesFromComponents(
|
|
391
|
-
|
|
392
|
-
components
|
|
393
|
-
config
|
|
374
|
+
projects[0],
|
|
375
|
+
components
|
|
394
376
|
);
|
|
395
377
|
issues = issues.concat(componentIssues);
|
|
396
378
|
}
|
|
@@ -423,19 +405,39 @@ async function createRouter(options) {
|
|
|
423
405
|
response.status(400).json(error);
|
|
424
406
|
return;
|
|
425
407
|
}
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
408
|
+
const getUserIssuesForInstance = async (instance) => {
|
|
409
|
+
const username = getAssigneUser(instance, userEntity);
|
|
410
|
+
const maxResults = Number(
|
|
411
|
+
request.query.maxResults || DEFAULT_MAX_RESULTS_USER_ISSUES
|
|
412
|
+
);
|
|
413
|
+
try {
|
|
414
|
+
const issues2 = await getUserIssues(
|
|
415
|
+
username,
|
|
416
|
+
maxResults,
|
|
417
|
+
instance,
|
|
418
|
+
cache$1
|
|
419
|
+
);
|
|
420
|
+
return { issues: issues2, error: void 0 };
|
|
421
|
+
} catch (error) {
|
|
422
|
+
return { error };
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
const issuesList = await Promise.all(
|
|
426
|
+
config.getInstances().map(
|
|
427
|
+
(instanceName) => getUserIssuesForInstance(config.getInstance(instanceName))
|
|
428
|
+
)
|
|
429
429
|
);
|
|
430
|
-
|
|
431
|
-
|
|
430
|
+
const issues = issuesList.flatMap((list) => list.issues ?? []);
|
|
431
|
+
const errors = issuesList.flatMap((list) => list.error).filter((v) => !!v);
|
|
432
|
+
if (issues.length > 0 || errors.length === 0) {
|
|
432
433
|
response.status(200).json(issues);
|
|
433
|
-
}
|
|
434
|
-
|
|
434
|
+
} else {
|
|
435
|
+
const messages = errors.length > 1 ? `
|
|
436
|
+
${errors.map((err) => err.message).join("\n ")}` : ` ${errors[0].message}`;
|
|
437
|
+
logger.error(`Error during getting user issues:${messages}`);
|
|
435
438
|
response.status(503).json({
|
|
436
|
-
error: `Error during getting user issues
|
|
439
|
+
error: `Error during getting user issues:${messages}`
|
|
437
440
|
});
|
|
438
|
-
return;
|
|
439
441
|
}
|
|
440
442
|
});
|
|
441
443
|
router.get(
|
|
@@ -454,21 +456,26 @@ async function createRouter(options) {
|
|
|
454
456
|
response.status(500).json({ error: `No entity found for ${entityRef}` });
|
|
455
457
|
return;
|
|
456
458
|
}
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
459
|
+
const fullProjectKeys = entity.metadata.annotations?.[projectKeyAnnotation]?.split(",");
|
|
460
|
+
if (!fullProjectKeys) {
|
|
461
|
+
const error = `No jira.com/project-key annotation found for ${entityRef}`;
|
|
462
|
+
logger.info(error);
|
|
463
|
+
response.status(404).json(error);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const projects = fullProjectKeys.map(
|
|
467
|
+
(fullProjectKey) => splitProjectKey(config, fullProjectKey)
|
|
462
468
|
);
|
|
469
|
+
const projectResponse = await getProjectResponse(projects[0], cache$1);
|
|
463
470
|
if (!projectResponse) {
|
|
464
471
|
logger.error("Could not find project in Jira");
|
|
465
472
|
response.status(400).json({
|
|
466
|
-
error: `No Jira project found for project key ${
|
|
473
|
+
error: `No Jira project found for project key ${projects[0].projectKey}`
|
|
467
474
|
});
|
|
468
475
|
return;
|
|
469
476
|
}
|
|
470
477
|
const url = projectResponse.avatarUrls["48x48"];
|
|
471
|
-
const avatar = await getProjectAvatar(url,
|
|
478
|
+
const avatar = await getProjectAvatar(url, projects[0].instance);
|
|
472
479
|
const ps = new stream__default.default.PassThrough();
|
|
473
480
|
const val = avatar.headers.get("content-type");
|
|
474
481
|
response.setHeader("content-type", val ?? "");
|
|
@@ -482,49 +489,120 @@ async function createRouter(options) {
|
|
|
482
489
|
ps.pipe(response);
|
|
483
490
|
}
|
|
484
491
|
);
|
|
485
|
-
const middleware = rootHttpRouter.MiddlewareFactory.create({ logger, config });
|
|
492
|
+
const middleware = rootHttpRouter.MiddlewareFactory.create({ logger, config: rootConfig });
|
|
486
493
|
router.use(middleware.error());
|
|
487
494
|
return router;
|
|
488
495
|
}
|
|
489
496
|
|
|
497
|
+
const JIRA_CONFIG_BASE_URL = "baseUrl";
|
|
498
|
+
const JIRA_CONFIG_TOKEN = "token";
|
|
499
|
+
const JIRA_CONFIG_USER_EMAIL_SUFFIX = "userEmailSuffix";
|
|
500
|
+
const JIRA_CONFIG_ANNOTATION = "annotationPrefix";
|
|
501
|
+
class JiraConfig {
|
|
502
|
+
instances = {};
|
|
503
|
+
annotationPrefix;
|
|
504
|
+
constructor(config) {
|
|
505
|
+
const jira = config.getConfig("jiraDashboard");
|
|
506
|
+
this.annotationPrefix = jira.getOptionalString(JIRA_CONFIG_ANNOTATION) ?? "jira.com";
|
|
507
|
+
const instances = jira.getOptionalConfigArray("instances");
|
|
508
|
+
if (instances) {
|
|
509
|
+
instances.forEach((inst) => {
|
|
510
|
+
const name = inst.getString("name");
|
|
511
|
+
if (Object.getOwnPropertyNames(this.instances).includes(name)) {
|
|
512
|
+
throw new errors.ConflictError(
|
|
513
|
+
`Duplicate jiraDashboard instances: '${name}'`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
this.instances[name] = {
|
|
517
|
+
token: inst.getString(JIRA_CONFIG_TOKEN),
|
|
518
|
+
baseUrl: inst.getString(JIRA_CONFIG_BASE_URL),
|
|
519
|
+
userEmailSuffix: inst.getOptionalString(
|
|
520
|
+
JIRA_CONFIG_USER_EMAIL_SUFFIX
|
|
521
|
+
)
|
|
522
|
+
};
|
|
523
|
+
});
|
|
524
|
+
} else {
|
|
525
|
+
this.instances.default = {
|
|
526
|
+
token: jira.getString(JIRA_CONFIG_TOKEN),
|
|
527
|
+
baseUrl: jira.getString(JIRA_CONFIG_BASE_URL),
|
|
528
|
+
userEmailSuffix: jira.getOptionalString(JIRA_CONFIG_USER_EMAIL_SUFFIX)
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
static fromConfig(config) {
|
|
533
|
+
return new JiraConfig(config);
|
|
534
|
+
}
|
|
535
|
+
forInstance(instanceName) {
|
|
536
|
+
const instance = this.instances[instanceName];
|
|
537
|
+
if (!instance) {
|
|
538
|
+
throw new errors.ServiceUnavailableError(
|
|
539
|
+
`No such jira instance '${instanceName}'`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return instance;
|
|
543
|
+
}
|
|
544
|
+
getInstances() {
|
|
545
|
+
return Object.getOwnPropertyNames(this.instances);
|
|
546
|
+
}
|
|
547
|
+
getInstance(instanceName) {
|
|
548
|
+
return this.forInstance(instanceName ?? "default");
|
|
549
|
+
}
|
|
550
|
+
resolveJiraBaseUrl(instanceName) {
|
|
551
|
+
const instance = this.forInstance(instanceName);
|
|
552
|
+
return instance.baseUrl;
|
|
553
|
+
}
|
|
554
|
+
resolveJiraToken(instanceName) {
|
|
555
|
+
const instance = this.forInstance(instanceName);
|
|
556
|
+
return instance.token;
|
|
557
|
+
}
|
|
558
|
+
resolveUserEmailSuffix(instanceName) {
|
|
559
|
+
const instance = this.forInstance(instanceName);
|
|
560
|
+
return instance.userEmailSuffix;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
490
564
|
const jiraDashboardPlugin = backendPluginApi.createBackendPlugin({
|
|
491
565
|
pluginId: "jira-dashboard",
|
|
492
566
|
register(env) {
|
|
493
567
|
env.registerInit({
|
|
494
568
|
deps: {
|
|
569
|
+
auth: backendPluginApi.coreServices.auth,
|
|
570
|
+
httpRouter: backendPluginApi.coreServices.httpRouter,
|
|
495
571
|
logger: backendPluginApi.coreServices.logger,
|
|
496
|
-
|
|
572
|
+
rootConfig: backendPluginApi.coreServices.rootConfig,
|
|
497
573
|
discovery: backendPluginApi.coreServices.discovery,
|
|
498
|
-
httpRouter: backendPluginApi.coreServices.httpRouter,
|
|
499
|
-
auth: backendPluginApi.coreServices.auth,
|
|
500
574
|
httpAuth: backendPluginApi.coreServices.httpAuth,
|
|
501
575
|
userInfo: backendPluginApi.coreServices.userInfo
|
|
502
576
|
},
|
|
503
577
|
async init({
|
|
578
|
+
auth,
|
|
579
|
+
httpRouter,
|
|
504
580
|
logger,
|
|
505
|
-
|
|
581
|
+
rootConfig,
|
|
506
582
|
discovery,
|
|
507
|
-
httpRouter,
|
|
508
|
-
auth,
|
|
509
583
|
httpAuth,
|
|
510
584
|
userInfo
|
|
511
585
|
}) {
|
|
512
586
|
httpRouter.use(
|
|
513
587
|
await createRouter({
|
|
588
|
+
auth,
|
|
514
589
|
logger,
|
|
515
|
-
|
|
590
|
+
rootConfig,
|
|
591
|
+
config: JiraConfig.fromConfig(rootConfig),
|
|
516
592
|
discovery,
|
|
517
|
-
auth,
|
|
518
593
|
httpAuth,
|
|
519
594
|
userInfo
|
|
520
595
|
})
|
|
521
596
|
);
|
|
597
|
+
httpRouter.addAuthPolicy({
|
|
598
|
+
path: "/health",
|
|
599
|
+
allow: "unauthenticated"
|
|
600
|
+
});
|
|
522
601
|
}
|
|
523
602
|
});
|
|
524
603
|
}
|
|
525
604
|
});
|
|
526
605
|
|
|
527
|
-
exports.createRouter = createRouter;
|
|
528
606
|
exports.default = jiraDashboardPlugin;
|
|
529
607
|
exports.jqlQueryBuilder = jqlQueryBuilder;
|
|
530
608
|
exports.searchJira = searchJira;
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/config.ts","../src/filters.ts","../src/queries.ts","../src/api.ts","../src/service/service.ts","../src/lib.ts","../src/service/router.ts","../src/plugin.ts"],"sourcesContent":["import { RootConfigService } from '@backstage/backend-plugin-api';\n\nconst JIRA_BASE_URL_CONFIG_PATH = 'jiraDashboard.baseUrl';\nconst JIRA_TOKEN_CONFIG_PATH = 'jiraDashboard.token';\nconst JIRA_USER_CONFIG_EMAIL_SUFFIX = 'jiraDashboard.userEmailSuffix';\nconst JIRA_ANNOTATION = 'jiraDashboard.annotationPrefix';\n\nexport function resolveJiraBaseUrl(config: RootConfigService): 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: RootConfigService): 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(\n config: RootConfigService,\n): string | undefined {\n return config.getOptionalString(JIRA_USER_CONFIG_EMAIL_SUFFIX);\n}\n\nexport function resolveAnnotationPrefix(config: RootConfigService): string {\n const annotationPrefix = config.getOptionalString(JIRA_ANNOTATION);\n return annotationPrefix ?? 'jira.com';\n}\n","import { RootConfigService } from '@backstage/backend-plugin-api';\nimport { resolveUserEmailSuffix } from './config';\nimport { Filter } from '@axis-backstage/plugin-jira-dashboard-common';\nimport { UserEntity } from '@backstage/catalog-model';\n\nconst openFilter: Filter = {\n name: 'Open Issues',\n shortName: 'OPEN',\n query: 'resolution = Unresolved ORDER BY updated DESC',\n};\n\nconst getIncomingFilter = (incomingStatus: string): Filter => ({\n name: 'Incoming Issues',\n shortName: 'INCOMING',\n query: `status = '${incomingStatus}' ORDER BY created ASC`,\n});\n\n/**\n * Get the username value for Jira assignee from the user entity.\n * If the email suffix is defined, the result combines `metadata.name` with the email suffix - otherwise if the user entity has a profile email or the metadata name is used.\n * Otherwise, the user entity name will be used.\n * @param config appConfig configuration\n * @param userEntity user entity instance\n */\nexport const getAssigneUser = (\n config: RootConfigService,\n userEntity: UserEntity,\n): string => {\n const emailSuffixConfig = resolveUserEmailSuffix(config);\n\n return emailSuffixConfig\n ? `${userEntity.metadata.name}${emailSuffixConfig}`\n : userEntity.spec?.profile?.email || userEntity.metadata.name;\n};\n\nconst getAssignedToMeFilter = (\n userEntity: UserEntity,\n config: RootConfigService,\n): Filter => {\n const email = getAssigneUser(config, userEntity);\n\n return {\n name: 'Assigned to me',\n shortName: 'ME',\n query: `assignee = \"${email}\" AND resolution = Unresolved ORDER BY updated DESC`,\n };\n};\n\nexport const getDefaultFiltersForUser = (\n config: RootConfigService,\n userEntity?: UserEntity,\n incomingStatus?: string,\n): Filter[] => {\n const incomingFilter = getIncomingFilter(incomingStatus ?? 'New');\n\n if (!userEntity) return [openFilter, incomingFilter];\n\n const assigneeToMeFilter = getAssignedToMeFilter(userEntity, config);\n\n return [openFilter, incomingFilter, assigneeToMeFilter];\n};\n","/**\n * Types for the arguments to the jqlQueryBuilder function.\n *\n * @public\n */\nexport type JqlQueryBuilderArgs = {\n project: string;\n components?: string[];\n query?: string;\n};\n\n/**\n * Creates a jql query string.\n *\n * @public\n */\nexport const jqlQueryBuilder = ({\n project,\n components,\n query,\n}: JqlQueryBuilderArgs) => {\n let jql = `project in (${project})`;\n if (components && components.length > 0) {\n let componentsInclude = '(';\n for (let index = 0; index < components.length; index++) {\n const component = components[index];\n componentsInclude += `'${component}'`;\n // Add either the \",\" separator or close the parentheses.\n if (index === components.length - 1) {\n componentsInclude += ')';\n } else {\n componentsInclude += ',';\n }\n }\n jql += ` AND component in ${componentsInclude}`;\n }\n if (query) {\n jql += ` AND ${query}`;\n }\n return jql;\n};\n","import { RootConfigService } from '@backstage/backend-plugin-api';\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';\nimport { jqlQueryBuilder } from './queries';\n\nexport const getProjectInfo = async (\n projectKey: string,\n config: RootConfigService,\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(\n `Request failed with status code ${response.status}: ${response.statusText}`,\n );\n }\n return response.json();\n};\n\nexport const getFilterById = async (\n id: string,\n config: RootConfigService,\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 components: string[],\n query: string,\n config: RootConfigService,\n): Promise<Issue[]> => {\n const jql = jqlQueryBuilder({ project: projectKey, components, query });\n const response = await fetch(\n `${resolveJiraBaseUrl(config)}search?jql=${jql}`,\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\n/**\n * Options available for the Jira JQL query.\n *\n * @public\n */\nexport type SearchOptions = {\n expand?: string[];\n fields?: string[];\n fieldsByKey?: boolean;\n properties?: string[];\n startAt?: number;\n maxResults?: number;\n validateQuery?: string;\n};\n\n/**\n * Search for Jira issues using JQL.\n *\n * For more information about the available options see the API\n * documentation at:\n * https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-search/#api-rest-api-2-search-post\n *\n * @param config - A Backstage config\n * @param jqlQuery - A string containing the jql query.\n * @param options - Query options that will be passed on to the POST request.\n *\n * @public\n */\nexport const searchJira = async (\n config: RootConfigService,\n jqlQuery: string,\n options: SearchOptions,\n): Promise<Issue[]> => {\n const response = await fetch(`${resolveJiraBaseUrl(config)}search`, {\n method: 'POST',\n body: JSON.stringify({ jql: jqlQuery, ...options }),\n headers: {\n Authorization: resolveJiraToken(config),\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n }).then(resp => resp.json());\n return response.issues;\n};\n\nexport const getIssuesByComponent = async (\n projectKey: string,\n componentKey: string,\n config: RootConfigService,\n): Promise<Issue[]> => {\n const jql = jqlQueryBuilder({\n project: projectKey,\n components: [componentKey],\n });\n const response = await fetch(\n `${resolveJiraBaseUrl(config)}search?jql=${jql}`,\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: RootConfigService) {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: resolveJiraToken(config),\n },\n });\n return response;\n}\n","import { CacheService, RootConfigService } from '@backstage/backend-plugin-api';\nimport {\n type Filter,\n Issue,\n type JiraDataResponse,\n type Project,\n} from '@axis-backstage/plugin-jira-dashboard-common';\nimport {\n getFilterById,\n getIssuesByComponent,\n getIssuesByFilter,\n getProjectInfo,\n searchJira,\n SearchOptions,\n} from '../api';\nimport { jqlQueryBuilder } from '../queries';\n\nexport const getProjectResponse = async (\n projectKey: string,\n config: RootConfigService,\n cache: CacheService,\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(\n `Failed to get project info for project key ${projectKey} with error: ${err.message}`,\n );\n }\n }\n return projectResponse;\n};\n\nexport const getJqlResponse = async (\n jql: string,\n config: RootConfigService,\n cache: CacheService,\n searchOptions: SearchOptions,\n): Promise<Issue[]> => {\n let issuesResponse: Issue[];\n\n issuesResponse = (await cache.get(jql)) as Issue[];\n\n if (issuesResponse) return issuesResponse as Issue[];\n\n try {\n issuesResponse = await searchJira(config, jql, searchOptions);\n cache.set(jql, issuesResponse);\n } catch (err: any) {\n if (err.message !== 200) {\n throw Error(\n `Failed to get issues for JQL ${jql} with error: ${err.message}`,\n );\n }\n }\n return issuesResponse;\n};\n\nexport const getUserIssues = async (\n username: string,\n maxResults: number,\n config: RootConfigService,\n cache: CacheService,\n): Promise<Issue[]> => {\n const jql = `assignee = \"${username}\" AND resolution = Unresolved ORDER BY priority DESC, updated DESC`;\n\n return getJqlResponse(jql, config, cache, {\n fields: [\n 'key',\n 'issuetype',\n 'summary',\n 'status',\n 'priority',\n 'created',\n 'updated',\n ],\n maxResults,\n });\n};\n\nexport const getFiltersFromAnnotations = async (\n annotations: string[],\n config: RootConfigService,\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 components: string[],\n filters: Filter[],\n config: RootConfigService,\n): Promise<JiraDataResponse[]> => {\n return await Promise.all(\n filters.map(async filter => ({\n name: filter.name,\n query: jqlQueryBuilder({\n project: projectKey,\n components,\n query: filter.query,\n }),\n type: 'filter',\n issues: await getIssuesByFilter(\n projectKey,\n components,\n filter.query,\n config,\n ),\n })),\n );\n};\n\nexport const getIssuesFromComponents = async (\n projectKey: string,\n componentAnnotations: string[],\n config: RootConfigService,\n): Promise<JiraDataResponse[]> => {\n return await Promise.all(\n componentAnnotations.map(async componentKey => ({\n name: componentKey,\n query: jqlQueryBuilder({\n project: projectKey,\n components: [componentKey],\n }),\n type: 'component',\n issues: await getIssuesByComponent(projectKey, componentKey, config),\n })),\n );\n};\n","import { resolveAnnotationPrefix } from './config';\nimport {\n COMPONENTS_NAME,\n PROJECT_KEY_NAME,\n FILTERS_NAME,\n INCOMING_ISSUES_STATUS,\n} from '@axis-backstage/plugin-jira-dashboard-common';\nimport { RootConfigService } from '@backstage/backend-plugin-api';\n\nexport const getAnnotations = (config: RootConfigService) => {\n const prefix = resolveAnnotationPrefix(config);\n\n const projectKeyAnnotation = `${prefix}/${PROJECT_KEY_NAME}`;\n const componentsAnnotation = `${prefix}/${COMPONENTS_NAME}`;\n const filtersAnnotation = `${prefix}/${FILTERS_NAME}`;\n const incomingIssuesAnnotation = `${prefix}/${INCOMING_ISSUES_STATUS}`;\n\n /* Adding support for Roadie's component annotation */\n const componentRoadieAnnotation = `${prefix}/component`;\n\n return {\n projectKeyAnnotation,\n componentsAnnotation,\n filtersAnnotation,\n incomingIssuesAnnotation,\n componentRoadieAnnotation,\n };\n};\n","import { createLegacyAuthAdapters } from '@backstage/backend-common';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport { CacheManager } from '@backstage/backend-defaults/cache';\nimport {\n AuthService,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n IdentityService,\n RootConfigService,\n TokenManagerService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { stringifyEntityRef, UserEntity } from '@backstage/catalog-model';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { CatalogClient } from '@backstage/catalog-client';\n\nimport { getAssigneUser, getDefaultFiltersForUser } from '../filters';\nimport {\n type Filter,\n type JiraResponse,\n type Project,\n} from '@axis-backstage/plugin-jira-dashboard-common';\nimport stream from 'stream';\nimport { getProjectAvatar } from '../api';\nimport {\n getFiltersFromAnnotations,\n getIssuesFromComponents,\n getIssuesFromFilters,\n getProjectResponse,\n getUserIssues,\n} from './service';\nimport { getAnnotations } from '../lib';\n\n/**\n * Constructs a jira dashboard router.\n * @deprecated Please migrate to the new backend system as this will be removed in the future.\n * @public\n */\nexport interface RouterOptions {\n /**\n * Implementation of Winston logger\n */\n logger: LoggerService;\n\n /**\n * Backstage config object\n */\n config: RootConfigService;\n\n /**\n * Backstage discovery api instance\n */\n discovery: DiscoveryService;\n\n /**\n * Backstage identity api instance\n */\n identity?: IdentityService;\n\n /**\n * Backstage token manager instance\n */\n tokenManager?: TokenManagerService;\n /**\n * Backstage auth service\n */\n auth?: AuthService;\n /**\n * Backstage httpAuth service\n */\n httpAuth?: HttpAuthService;\n\n /**\n * Backstage userInfo service\n */\n userInfo: UserInfoService;\n}\n\nconst DEFAULT_TTL = 1000 * 60;\n\nconst DEFAULT_MAX_RESULTS_USER_ISSUES = 10;\n\n/**\n * Constructs a jira dashboard router.\n *\n * @deprecated Please migrate to the new backend system as this will be removed in the future.\n * @public\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { auth, httpAuth } = createLegacyAuthAdapters(options);\n const { logger, config, discovery, userInfo } = options;\n const catalogClient = new CatalogClient({ discoveryApi: discovery });\n\n const pluginCache =\n CacheManager.fromConfig(config).forPlugin('jira-dashboard');\n const cache = pluginCache.getClient({ defaultTtl: DEFAULT_TTL });\n logger.info('Initializing Jira Dashboard backend');\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/:kind/:namespace/:name',\n async (request, response) => {\n const { kind, namespace, name } = request.params;\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await catalogClient.getEntityByRef(entityRef, { token });\n const {\n projectKeyAnnotation,\n componentsAnnotation,\n filtersAnnotation,\n incomingIssuesAnnotation,\n componentRoadieAnnotation,\n } = getAnnotations(config);\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 =\n entity.metadata.annotations?.[projectKeyAnnotation]?.split(',')!;\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(\n projectKey[0],\n config,\n cache,\n );\n } catch (err: any) {\n logger.error(\n `Could not find Jira project ${projectKey[0]}: ${err.message}`,\n );\n response.status(404).json({\n error: `No Jira project found with key ${projectKey[0]}`,\n });\n return;\n }\n\n let userEntity: UserEntity | undefined;\n\n try {\n const credentials = await httpAuth.credentials(request, {\n allow: ['user'],\n });\n const userIdentity = credentials.principal.userEntityRef;\n\n userEntity = (await catalogClient.getEntityByRef(userIdentity, {\n token,\n })) as UserEntity;\n } catch (err) {\n logger.warn('Could not find user identity');\n }\n\n let filters: Filter[] = [];\n\n const incomingStatus =\n entity.metadata.annotations?.[incomingIssuesAnnotation];\n\n filters = getDefaultFiltersForUser(config, userEntity, incomingStatus);\n\n const customFilterAnnotations =\n entity.metadata.annotations?.[filtersAnnotation]?.split(',')!;\n\n if (customFilterAnnotations) {\n filters.push(\n ...(await getFiltersFromAnnotations(customFilterAnnotations, config)),\n );\n }\n\n let components =\n entity.metadata.annotations?.[componentsAnnotation]?.split(',') ?? [];\n let issues = await getIssuesFromFilters(\n projectKey[0],\n components,\n filters,\n config,\n );\n\n /* Adding support for Roadie's component annotation */\n components = components.concat(\n entity.metadata.annotations?.[componentRoadieAnnotation]?.split(',') ??\n [],\n );\n\n if (components) {\n const componentIssues = await getIssuesFromComponents(\n projectKey[0],\n components,\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('/dashboards/user-issues', async (request, response) => {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n\n const credentials = await httpAuth.credentials(request, {\n allow: ['user'],\n });\n\n // we ignore guest and service users, no issues in response\n if (!auth.isPrincipal(credentials, 'user')) {\n response.status(200).json([]);\n return;\n }\n\n const info = await userInfo.getUserInfo(credentials);\n\n const userEntity = (await catalogClient.getEntityByRef(info.userEntityRef, {\n token,\n })) as UserEntity;\n\n if (!userEntity) {\n const error = `User entity cannot be determined from ${info.userEntityRef}`;\n logger.info(error);\n response.status(400).json(error);\n return;\n }\n\n const username = getAssigneUser(config, userEntity);\n\n const maxResults = Number(\n request.query.maxResults || DEFAULT_MAX_RESULTS_USER_ISSUES,\n );\n\n try {\n const issues = await getUserIssues(username, maxResults, config, cache);\n response.status(200).json(issues);\n } catch (err: any) {\n logger.error(`Error during getting user issues: ${err.message}`);\n response.status(503).json({\n error: `Error during getting user issues: ${err.message}`,\n });\n return;\n }\n });\n\n router.get(\n '/avatar/by-entity-ref/:kind/:namespace/:name',\n async (request, response) => {\n const { kind, namespace, name } = request.params;\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await catalogClient.getEntityByRef(entityRef, { token });\n const { projectKeyAnnotation } = getAnnotations(config);\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 =\n entity.metadata.annotations?.[projectKeyAnnotation]?.split(',')!;\n\n const projectResponse = await getProjectResponse(\n projectKey[0],\n config,\n cache,\n );\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[0]}`,\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 );\n const middleware = MiddlewareFactory.create({ logger, config });\n router.use(middleware.error());\n return router;\n}\n","import {\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 httpRouter: coreServices.httpRouter,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n userInfo: coreServices.userInfo,\n },\n async init({\n logger,\n config,\n discovery,\n httpRouter,\n auth,\n httpAuth,\n userInfo,\n }) {\n httpRouter.use(\n await createRouter({\n logger,\n config,\n discovery,\n auth,\n httpAuth,\n userInfo,\n }),\n );\n },\n });\n },\n});\n"],"names":["fetch","PROJECT_KEY_NAME","COMPONENTS_NAME","FILTERS_NAME","INCOMING_ISSUES_STATUS","createLegacyAuthAdapters","catalogClient","CatalogClient","CacheManager","cache","Router","express","stringifyEntityRef","stream","MiddlewareFactory","createBackendPlugin","coreServices"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAEA,MAAM,yBAA4B,GAAA,uBAAA,CAAA;AAClC,MAAM,sBAAyB,GAAA,qBAAA,CAAA;AAC/B,MAAM,6BAAgC,GAAA,+BAAA,CAAA;AACtC,MAAM,eAAkB,GAAA,gCAAA,CAAA;AAEjB,SAAS,mBAAmB,MAAmC,EAAA;AACpE,EAAI,IAAA;AACF,IAAO,OAAA,MAAA,CAAO,UAAU,yBAAyB,CAAA,CAAA;AAAA,WAC1C,KAAO,EAAA;AACd,IAAA,MAAM,IAAI,KAAA,CAAM,CAAyB,sBAAA,EAAA,KAAK,CAAE,CAAA,CAAA,CAAA;AAAA,GAClD;AACF,CAAA;AAEO,SAAS,iBAAiB,MAAmC,EAAA;AAClE,EAAI,IAAA;AACF,IAAO,OAAA,MAAA,CAAO,UAAU,sBAAsB,CAAA,CAAA;AAAA,WACvC,KAAO,EAAA;AACd,IAAA,MAAM,IAAI,KAAA,CAAM,CAAuB,oBAAA,EAAA,KAAK,CAAE,CAAA,CAAA,CAAA;AAAA,GAChD;AACF,CAAA;AAEO,SAAS,uBACd,MACoB,EAAA;AACpB,EAAO,OAAA,MAAA,CAAO,kBAAkB,6BAA6B,CAAA,CAAA;AAC/D,CAAA;AAEO,SAAS,wBAAwB,MAAmC,EAAA;AACzE,EAAM,MAAA,gBAAA,GAAmB,MAAO,CAAA,iBAAA,CAAkB,eAAe,CAAA,CAAA;AACjE,EAAA,OAAO,gBAAoB,IAAA,UAAA,CAAA;AAC7B;;AC3BA,MAAM,UAAqB,GAAA;AAAA,EACzB,IAAM,EAAA,aAAA;AAAA,EACN,SAAW,EAAA,MAAA;AAAA,EACX,KAAO,EAAA,+CAAA;AACT,CAAA,CAAA;AAEA,MAAM,iBAAA,GAAoB,CAAC,cAAoC,MAAA;AAAA,EAC7D,IAAM,EAAA,iBAAA;AAAA,EACN,SAAW,EAAA,UAAA;AAAA,EACX,KAAA,EAAO,aAAa,cAAc,CAAA,sBAAA,CAAA;AACpC,CAAA,CAAA,CAAA;AASa,MAAA,cAAA,GAAiB,CAC5B,MAAA,EACA,UACW,KAAA;AACX,EAAM,MAAA,iBAAA,GAAoB,uBAAuB,MAAM,CAAA,CAAA;AAEvD,EAAA,OAAO,iBACH,GAAA,CAAA,EAAG,UAAW,CAAA,QAAA,CAAS,IAAI,CAAA,EAAG,iBAAiB,CAAA,CAAA,GAC/C,UAAW,CAAA,IAAA,EAAM,OAAS,EAAA,KAAA,IAAS,WAAW,QAAS,CAAA,IAAA,CAAA;AAC7D,CAAA,CAAA;AAEA,MAAM,qBAAA,GAAwB,CAC5B,UAAA,EACA,MACW,KAAA;AACX,EAAM,MAAA,KAAA,GAAQ,cAAe,CAAA,MAAA,EAAQ,UAAU,CAAA,CAAA;AAE/C,EAAO,OAAA;AAAA,IACL,IAAM,EAAA,gBAAA;AAAA,IACN,SAAW,EAAA,IAAA;AAAA,IACX,KAAA,EAAO,eAAe,KAAK,CAAA,mDAAA,CAAA;AAAA,GAC7B,CAAA;AACF,CAAA,CAAA;AAEO,MAAM,wBAA2B,GAAA,CACtC,MACA,EAAA,UAAA,EACA,cACa,KAAA;AACb,EAAM,MAAA,cAAA,GAAiB,iBAAkB,CAAA,cAAA,IAAkB,KAAK,CAAA,CAAA;AAEhE,EAAA,IAAI,CAAC,UAAA,EAAmB,OAAA,CAAC,YAAY,cAAc,CAAA,CAAA;AAEnD,EAAM,MAAA,kBAAA,GAAqB,qBAAsB,CAAA,UAAA,EAAY,MAAM,CAAA,CAAA;AAEnE,EAAO,OAAA,CAAC,UAAY,EAAA,cAAA,EAAgB,kBAAkB,CAAA,CAAA;AACxD,CAAA;;AC5CO,MAAM,kBAAkB,CAAC;AAAA,EAC9B,OAAA;AAAA,EACA,UAAA;AAAA,EACA,KAAA;AACF,CAA2B,KAAA;AACzB,EAAI,IAAA,GAAA,GAAM,eAAe,OAAO,CAAA,CAAA,CAAA,CAAA;AAChC,EAAI,IAAA,UAAA,IAAc,UAAW,CAAA,MAAA,GAAS,CAAG,EAAA;AACvC,IAAA,IAAI,iBAAoB,GAAA,GAAA,CAAA;AACxB,IAAA,KAAA,IAAS,KAAQ,GAAA,CAAA,EAAG,KAAQ,GAAA,UAAA,CAAW,QAAQ,KAAS,EAAA,EAAA;AACtD,MAAM,MAAA,SAAA,GAAY,WAAW,KAAK,CAAA,CAAA;AAClC,MAAA,iBAAA,IAAqB,IAAI,SAAS,CAAA,CAAA,CAAA,CAAA;AAElC,MAAI,IAAA,KAAA,KAAU,UAAW,CAAA,MAAA,GAAS,CAAG,EAAA;AACnC,QAAqB,iBAAA,IAAA,GAAA,CAAA;AAAA,OAChB,MAAA;AACL,QAAqB,iBAAA,IAAA,GAAA,CAAA;AAAA,OACvB;AAAA,KACF;AACA,IAAA,GAAA,IAAO,qBAAqB,iBAAiB,CAAA,CAAA,CAAA;AAAA,GAC/C;AACA,EAAA,IAAI,KAAO,EAAA;AACT,IAAA,GAAA,IAAO,QAAQ,KAAK,CAAA,CAAA,CAAA;AAAA,GACtB;AACA,EAAO,OAAA,GAAA,CAAA;AACT;;AC9Ba,MAAA,cAAA,GAAiB,OAC5B,UAAA,EACA,MACqB,KAAA;AACrB,EAAA,MAAM,WAAW,MAAMA,sBAAA;AAAA,IACrB,CAAG,EAAA,kBAAA,CAAmB,MAAM,CAAC,WAAW,UAAU,CAAA,CAAA;AAAA,IAClD;AAAA,MACE,MAAQ,EAAA,KAAA;AAAA,MACR,OAAS,EAAA;AAAA,QACP,aAAA,EAAe,iBAAiB,MAAM,CAAA;AAAA,QACtC,MAAQ,EAAA,kBAAA;AAAA,OACV;AAAA,KACF;AAAA,GACF,CAAA;AACA,EAAI,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AAC3B,IAAM,MAAA,KAAA;AAAA,MACJ,CAAmC,gCAAA,EAAA,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA,CAAA;AAAA,KAC5E,CAAA;AAAA,GACF;AACA,EAAA,OAAO,SAAS,IAAK,EAAA,CAAA;AACvB,CAAA,CAAA;AAEa,MAAA,aAAA,GAAgB,OAC3B,EAAA,EACA,MACoB,KAAA;AACpB,EAAM,MAAA,QAAA,GAAW,MAAMA,sBAAM,CAAA,CAAA,EAAG,mBAAmB,MAAM,CAAC,CAAU,OAAA,EAAA,EAAE,CAAI,CAAA,EAAA;AAAA,IACxE,MAAQ,EAAA,KAAA;AAAA,IACR,OAAS,EAAA;AAAA,MACP,aAAA,EAAe,iBAAiB,MAAM,CAAA;AAAA,MACtC,MAAQ,EAAA,kBAAA;AAAA,KACV;AAAA,GACD,CAAA,CAAA;AACD,EAAI,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AAC3B,IAAA,MAAM,KAAM,CAAA,CAAA,EAAG,QAAS,CAAA,MAAM,CAAE,CAAA,CAAA,CAAA;AAAA,GAClC;AACA,EAAM,MAAA,YAAA,GAAe,MAAM,QAAA,CAAS,IAAK,EAAA,CAAA;AACzC,EAAA,OAAO,EAAE,IAAM,EAAA,YAAA,CAAa,IAAM,EAAA,KAAA,EAAO,aAAa,GAAI,EAAA,CAAA;AAC5D,CAAA,CAAA;AAEO,MAAM,iBAAoB,GAAA,OAC/B,UACA,EAAA,UAAA,EACA,OACA,MACqB,KAAA;AACrB,EAAA,MAAM,MAAM,eAAgB,CAAA,EAAE,SAAS,UAAY,EAAA,UAAA,EAAY,OAAO,CAAA,CAAA;AACtE,EAAA,MAAM,WAAW,MAAMA,sBAAA;AAAA,IACrB,CAAG,EAAA,kBAAA,CAAmB,MAAM,CAAC,cAAc,GAAG,CAAA,CAAA;AAAA,IAC9C;AAAA,MACE,MAAQ,EAAA,KAAA;AAAA,MACR,OAAS,EAAA;AAAA,QACP,aAAA,EAAe,iBAAiB,MAAM,CAAA;AAAA,QACtC,MAAQ,EAAA,kBAAA;AAAA,OACV;AAAA,KACF;AAAA,GACA,CAAA,IAAA,CAAK,CAAQ,IAAA,KAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAC1B,EAAA,OAAO,QAAS,CAAA,MAAA,CAAA;AAClB,CAAA,CAAA;AA8BO,MAAM,UAAa,GAAA,OACxB,MACA,EAAA,QAAA,EACA,OACqB,KAAA;AACrB,EAAA,MAAM,WAAW,MAAMA,sBAAA,CAAM,GAAG,kBAAmB,CAAA,MAAM,CAAC,CAAU,MAAA,CAAA,EAAA;AAAA,IAClE,MAAQ,EAAA,MAAA;AAAA,IACR,IAAA,EAAM,KAAK,SAAU,CAAA,EAAE,KAAK,QAAU,EAAA,GAAG,SAAS,CAAA;AAAA,IAClD,OAAS,EAAA;AAAA,MACP,aAAA,EAAe,iBAAiB,MAAM,CAAA;AAAA,MACtC,MAAQ,EAAA,kBAAA;AAAA,MACR,cAAgB,EAAA,kBAAA;AAAA,KAClB;AAAA,GACD,CAAE,CAAA,IAAA,CAAK,CAAQ,IAAA,KAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAC3B,EAAA,OAAO,QAAS,CAAA,MAAA,CAAA;AAClB,EAAA;AAEO,MAAM,oBAAuB,GAAA,OAClC,UACA,EAAA,YAAA,EACA,MACqB,KAAA;AACrB,EAAA,MAAM,MAAM,eAAgB,CAAA;AAAA,IAC1B,OAAS,EAAA,UAAA;AAAA,IACT,UAAA,EAAY,CAAC,YAAY,CAAA;AAAA,GAC1B,CAAA,CAAA;AACD,EAAA,MAAM,WAAW,MAAMA,sBAAA;AAAA,IACrB,CAAG,EAAA,kBAAA,CAAmB,MAAM,CAAC,cAAc,GAAG,CAAA,CAAA;AAAA,IAC9C;AAAA,MACE,MAAQ,EAAA,KAAA;AAAA,MACR,OAAS,EAAA;AAAA,QACP,aAAA,EAAe,iBAAiB,MAAM,CAAA;AAAA,QACtC,MAAQ,EAAA,kBAAA;AAAA,OACV;AAAA,KACF;AAAA,GACA,CAAA,IAAA,CAAK,CAAQ,IAAA,KAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAC1B,EAAA,OAAO,QAAS,CAAA,MAAA,CAAA;AAClB,CAAA,CAAA;AAEsB,eAAA,gBAAA,CAAiB,KAAa,MAA2B,EAAA;AAC7E,EAAM,MAAA,QAAA,GAAW,MAAMA,sBAAA,CAAM,GAAK,EAAA;AAAA,IAChC,MAAQ,EAAA,KAAA;AAAA,IACR,OAAS,EAAA;AAAA,MACP,aAAA,EAAe,iBAAiB,MAAM,CAAA;AAAA,KACxC;AAAA,GACD,CAAA,CAAA;AACD,EAAO,OAAA,QAAA,CAAA;AACT;;AChIO,MAAM,kBAAqB,GAAA,OAChC,UACA,EAAA,MAAA,EACA,KACqB,KAAA;AACrB,EAAI,IAAA,eAAA,CAAA;AAEJ,EAAmB,eAAA,GAAA,MAAM,KAAM,CAAA,GAAA,CAAI,UAAU,CAAA,CAAA;AAE7C,EAAA,IAAI,iBAAwB,OAAA,eAAA,CAAA;AAE5B,EAAI,IAAA;AACF,IAAkB,eAAA,GAAA,MAAM,cAAe,CAAA,UAAA,EAAY,MAAM,CAAA,CAAA;AACzD,IAAM,KAAA,CAAA,GAAA,CAAI,YAAY,eAAe,CAAA,CAAA;AAAA,WAC9B,GAAU,EAAA;AACjB,IAAI,IAAA,GAAA,CAAI,YAAY,GAAK,EAAA;AACvB,MAAM,MAAA,KAAA;AAAA,QACJ,CAA8C,2CAAA,EAAA,UAAU,CAAgB,aAAA,EAAA,GAAA,CAAI,OAAO,CAAA,CAAA;AAAA,OACrF,CAAA;AAAA,KACF;AAAA,GACF;AACA,EAAO,OAAA,eAAA,CAAA;AACT,CAAA,CAAA;AAEO,MAAM,cAAiB,GAAA,OAC5B,GACA,EAAA,MAAA,EACA,OACA,aACqB,KAAA;AACrB,EAAI,IAAA,cAAA,CAAA;AAEJ,EAAkB,cAAA,GAAA,MAAM,KAAM,CAAA,GAAA,CAAI,GAAG,CAAA,CAAA;AAErC,EAAA,IAAI,gBAAuB,OAAA,cAAA,CAAA;AAE3B,EAAI,IAAA;AACF,IAAA,cAAA,GAAiB,MAAM,UAAA,CAAW,MAAQ,EAAA,GAAA,EAAK,aAAa,CAAA,CAAA;AAC5D,IAAM,KAAA,CAAA,GAAA,CAAI,KAAK,cAAc,CAAA,CAAA;AAAA,WACtB,GAAU,EAAA;AACjB,IAAI,IAAA,GAAA,CAAI,YAAY,GAAK,EAAA;AACvB,MAAM,MAAA,KAAA;AAAA,QACJ,CAAgC,6BAAA,EAAA,GAAG,CAAgB,aAAA,EAAA,GAAA,CAAI,OAAO,CAAA,CAAA;AAAA,OAChE,CAAA;AAAA,KACF;AAAA,GACF;AACA,EAAO,OAAA,cAAA,CAAA;AACT,CAAA,CAAA;AAEO,MAAM,aAAgB,GAAA,OAC3B,QACA,EAAA,UAAA,EACA,QACA,KACqB,KAAA;AACrB,EAAM,MAAA,GAAA,GAAM,eAAe,QAAQ,CAAA,kEAAA,CAAA,CAAA;AAEnC,EAAO,OAAA,cAAA,CAAe,GAAK,EAAA,MAAA,EAAQ,KAAO,EAAA;AAAA,IACxC,MAAQ,EAAA;AAAA,MACN,KAAA;AAAA,MACA,WAAA;AAAA,MACA,SAAA;AAAA,MACA,QAAA;AAAA,MACA,UAAA;AAAA,MACA,SAAA;AAAA,MACA,SAAA;AAAA,KACF;AAAA,IACA,UAAA;AAAA,GACD,CAAA,CAAA;AACH,CAAA,CAAA;AAEa,MAAA,yBAAA,GAA4B,OACvC,WAAA,EACA,MACsB,KAAA;AACtB,EAAA,MAAM,UAAoB,EAAC,CAAA;AAE3B,EAAA,KAAA,MAAW,UAAU,WAAa,EAAA;AAChC,IAAI,IAAA;AACF,MAAA,MAAM,QAAW,GAAA,MAAM,aAAc,CAAA,MAAA,EAAQ,MAAM,CAAA,CAAA;AACnD,MAAA,OAAA,CAAQ,KAAK,QAAQ,CAAA,CAAA;AAAA,aACd,GAAU,EAAA;AACjB,MAAQ,OAAA,CAAA,IAAA;AAAA,QACN,CAAG,EAAA,GAAA,CAAI,OAAO,CAAA,wCAAA,EAA2C,MAAM,CAAA,CAAA;AAAA,OACjE,CAAA;AAAA,KACF;AAAA,GACF;AACA,EAAO,OAAA,OAAA,CAAA;AACT,CAAA,CAAA;AAEO,MAAM,oBAAuB,GAAA,OAClC,UACA,EAAA,UAAA,EACA,SACA,MACgC,KAAA;AAChC,EAAA,OAAO,MAAM,OAAQ,CAAA,GAAA;AAAA,IACnB,OAAA,CAAQ,GAAI,CAAA,OAAM,MAAW,MAAA;AAAA,MAC3B,MAAM,MAAO,CAAA,IAAA;AAAA,MACb,OAAO,eAAgB,CAAA;AAAA,QACrB,OAAS,EAAA,UAAA;AAAA,QACT,UAAA;AAAA,QACA,OAAO,MAAO,CAAA,KAAA;AAAA,OACf,CAAA;AAAA,MACD,IAAM,EAAA,QAAA;AAAA,MACN,QAAQ,MAAM,iBAAA;AAAA,QACZ,UAAA;AAAA,QACA,UAAA;AAAA,QACA,MAAO,CAAA,KAAA;AAAA,QACP,MAAA;AAAA,OACF;AAAA,KACA,CAAA,CAAA;AAAA,GACJ,CAAA;AACF,CAAA,CAAA;AAEO,MAAM,uBAA0B,GAAA,OACrC,UACA,EAAA,oBAAA,EACA,MACgC,KAAA;AAChC,EAAA,OAAO,MAAM,OAAQ,CAAA,GAAA;AAAA,IACnB,oBAAA,CAAqB,GAAI,CAAA,OAAM,YAAiB,MAAA;AAAA,MAC9C,IAAM,EAAA,YAAA;AAAA,MACN,OAAO,eAAgB,CAAA;AAAA,QACrB,OAAS,EAAA,UAAA;AAAA,QACT,UAAA,EAAY,CAAC,YAAY,CAAA;AAAA,OAC1B,CAAA;AAAA,MACD,IAAM,EAAA,WAAA;AAAA,MACN,MAAQ,EAAA,MAAM,oBAAqB,CAAA,UAAA,EAAY,cAAc,MAAM,CAAA;AAAA,KACnE,CAAA,CAAA;AAAA,GACJ,CAAA;AACF,CAAA;;AC3Ia,MAAA,cAAA,GAAiB,CAAC,MAA8B,KAAA;AAC3D,EAAM,MAAA,MAAA,GAAS,wBAAwB,MAAM,CAAA,CAAA;AAE7C,EAAA,MAAM,oBAAuB,GAAA,CAAA,EAAG,MAAM,CAAA,CAAA,EAAIC,0CAAgB,CAAA,CAAA,CAAA;AAC1D,EAAA,MAAM,oBAAuB,GAAA,CAAA,EAAG,MAAM,CAAA,CAAA,EAAIC,yCAAe,CAAA,CAAA,CAAA;AACzD,EAAA,MAAM,iBAAoB,GAAA,CAAA,EAAG,MAAM,CAAA,CAAA,EAAIC,sCAAY,CAAA,CAAA,CAAA;AACnD,EAAA,MAAM,wBAA2B,GAAA,CAAA,EAAG,MAAM,CAAA,CAAA,EAAIC,gDAAsB,CAAA,CAAA,CAAA;AAGpE,EAAM,MAAA,yBAAA,GAA4B,GAAG,MAAM,CAAA,UAAA,CAAA,CAAA;AAE3C,EAAO,OAAA;AAAA,IACL,oBAAA;AAAA,IACA,oBAAA;AAAA,IACA,iBAAA;AAAA,IACA,wBAAA;AAAA,IACA,yBAAA;AAAA,GACF,CAAA;AACF,CAAA;;ACqDA,MAAM,cAAc,GAAO,GAAA,EAAA,CAAA;AAE3B,MAAM,+BAAkC,GAAA,EAAA,CAAA;AAQxC,eAAsB,aACpB,OACyB,EAAA;AACzB,EAAA,MAAM,EAAE,IAAA,EAAM,QAAS,EAAA,GAAIC,uCAAyB,OAAO,CAAA,CAAA;AAC3D,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAQ,EAAA,SAAA,EAAW,UAAa,GAAA,OAAA,CAAA;AAChD,EAAA,MAAMC,kBAAgB,IAAIC,2BAAA,CAAc,EAAE,YAAA,EAAc,WAAW,CAAA,CAAA;AAEnE,EAAA,MAAM,cACJC,kBAAa,CAAA,UAAA,CAAW,MAAM,CAAA,CAAE,UAAU,gBAAgB,CAAA,CAAA;AAC5D,EAAA,MAAMC,UAAQ,WAAY,CAAA,SAAA,CAAU,EAAE,UAAA,EAAY,aAAa,CAAA,CAAA;AAC/D,EAAA,MAAA,CAAO,KAAK,qCAAqC,CAAA,CAAA;AAEjD,EAAA,MAAM,SAASC,uBAAO,EAAA,CAAA;AACtB,EAAO,MAAA,CAAA,GAAA,CAAIC,wBAAQ,CAAA,IAAA,EAAM,CAAA,CAAA;AAEzB,EAAA,MAAA,CAAO,GAAI,CAAA,SAAA,EAAW,CAAC,CAAA,EAAG,QAAa,KAAA;AACrC,IAAA,QAAA,CAAS,IAAK,CAAA,EAAE,MAAQ,EAAA,IAAA,EAAM,CAAA,CAAA;AAAA,GAC/B,CAAA,CAAA;AAED,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,kDAAA;AAAA,IACA,OAAO,SAAS,QAAa,KAAA;AAC3B,MAAA,MAAM,EAAE,IAAA,EAAM,SAAW,EAAA,IAAA,KAAS,OAAQ,CAAA,MAAA,CAAA;AAC1C,MAAA,MAAM,YAAYC,+BAAmB,CAAA,EAAE,IAAM,EAAA,SAAA,EAAW,MAAM,CAAA,CAAA;AAC9D,MAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,KAAK,qBAAsB,CAAA;AAAA,QACjD,UAAA,EAAY,MAAM,IAAA,CAAK,wBAAyB,EAAA;AAAA,QAChD,cAAgB,EAAA,SAAA;AAAA,OACjB,CAAA,CAAA;AACD,MAAA,MAAM,SAAS,MAAMN,eAAA,CAAc,eAAe,SAAW,EAAA,EAAE,OAAO,CAAA,CAAA;AACtE,MAAM,MAAA;AAAA,QACJ,oBAAA;AAAA,QACA,oBAAA;AAAA,QACA,iBAAA;AAAA,QACA,wBAAA;AAAA,QACA,yBAAA;AAAA,OACF,GAAI,eAAe,MAAM,CAAA,CAAA;AAEzB,MAAA,IAAI,CAAC,MAAQ,EAAA;AACX,QAAO,MAAA,CAAA,IAAA,CAAK,CAAuB,oBAAA,EAAA,SAAS,CAAE,CAAA,CAAA,CAAA;AAC9C,QACG,QAAA,CAAA,MAAA,CAAO,GAAG,CACV,CAAA,IAAA,CAAK,EAAE,KAAO,EAAA,CAAA,oBAAA,EAAuB,SAAS,CAAA,CAAA,EAAI,CAAA,CAAA;AACrD,QAAA,OAAA;AAAA,OACF;AAEA,MAAA,MAAM,aACJ,MAAO,CAAA,QAAA,CAAS,cAAc,oBAAoB,CAAA,EAAG,MAAM,GAAG,CAAA,CAAA;AAEhE,MAAA,IAAI,CAAC,UAAY,EAAA;AACf,QAAM,MAAA,KAAA,GAAQ,gDAAgD,SAAS,CAAA,CAAA,CAAA;AACvE,QAAA,MAAA,CAAO,KAAK,KAAK,CAAA,CAAA;AACjB,QAAA,QAAA,CAAS,MAAO,CAAA,GAAG,CAAE,CAAA,IAAA,CAAK,KAAK,CAAA,CAAA;AAC/B,QAAA,OAAA;AAAA,OACF;AAEA,MAAI,IAAA,eAAA,CAAA;AAEJ,MAAI,IAAA;AACF,QAAA,eAAA,GAAkB,MAAM,kBAAA;AAAA,UACtB,WAAW,CAAC,CAAA;AAAA,UACZ,MAAA;AAAA,UACAG,OAAA;AAAA,SACF,CAAA;AAAA,eACO,GAAU,EAAA;AACjB,QAAO,MAAA,CAAA,KAAA;AAAA,UACL,+BAA+B,UAAW,CAAA,CAAC,CAAC,CAAA,EAAA,EAAK,IAAI,OAAO,CAAA,CAAA;AAAA,SAC9D,CAAA;AACA,QAAS,QAAA,CAAA,MAAA,CAAO,GAAG,CAAA,CAAE,IAAK,CAAA;AAAA,UACxB,KAAO,EAAA,CAAA,+BAAA,EAAkC,UAAW,CAAA,CAAC,CAAC,CAAA,CAAA;AAAA,SACvD,CAAA,CAAA;AACD,QAAA,OAAA;AAAA,OACF;AAEA,MAAI,IAAA,UAAA,CAAA;AAEJ,MAAI,IAAA;AACF,QAAA,MAAM,WAAc,GAAA,MAAM,QAAS,CAAA,WAAA,CAAY,OAAS,EAAA;AAAA,UACtD,KAAA,EAAO,CAAC,MAAM,CAAA;AAAA,SACf,CAAA,CAAA;AACD,QAAM,MAAA,YAAA,GAAe,YAAY,SAAU,CAAA,aAAA,CAAA;AAE3C,QAAc,UAAA,GAAA,MAAMH,eAAc,CAAA,cAAA,CAAe,YAAc,EAAA;AAAA,UAC7D,KAAA;AAAA,SACD,CAAA,CAAA;AAAA,eACM,GAAK,EAAA;AACZ,QAAA,MAAA,CAAO,KAAK,8BAA8B,CAAA,CAAA;AAAA,OAC5C;AAEA,MAAA,IAAI,UAAoB,EAAC,CAAA;AAEzB,MAAA,MAAM,cACJ,GAAA,MAAA,CAAO,QAAS,CAAA,WAAA,GAAc,wBAAwB,CAAA,CAAA;AAExD,MAAU,OAAA,GAAA,wBAAA,CAAyB,MAAQ,EAAA,UAAA,EAAY,cAAc,CAAA,CAAA;AAErE,MAAA,MAAM,0BACJ,MAAO,CAAA,QAAA,CAAS,cAAc,iBAAiB,CAAA,EAAG,MAAM,GAAG,CAAA,CAAA;AAE7D,MAAA,IAAI,uBAAyB,EAAA;AAC3B,QAAQ,OAAA,CAAA,IAAA;AAAA,UACN,GAAI,MAAM,yBAA0B,CAAA,uBAAA,EAAyB,MAAM,CAAA;AAAA,SACrE,CAAA;AAAA,OACF;AAEA,MAAI,IAAA,UAAA,GACF,OAAO,QAAS,CAAA,WAAA,GAAc,oBAAoB,CAAG,EAAA,KAAA,CAAM,GAAG,CAAA,IAAK,EAAC,CAAA;AACtE,MAAA,IAAI,SAAS,MAAM,oBAAA;AAAA,QACjB,WAAW,CAAC,CAAA;AAAA,QACZ,UAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAA;AAAA,OACF,CAAA;AAGA,MAAA,UAAA,GAAa,UAAW,CAAA,MAAA;AAAA,QACtB,MAAA,CAAO,SAAS,WAAc,GAAA,yBAAyB,GAAG,KAAM,CAAA,GAAG,KACjE,EAAC;AAAA,OACL,CAAA;AAEA,MAAA,IAAI,UAAY,EAAA;AACd,QAAA,MAAM,kBAAkB,MAAM,uBAAA;AAAA,UAC5B,WAAW,CAAC,CAAA;AAAA,UACZ,UAAA;AAAA,UACA,MAAA;AAAA,SACF,CAAA;AACA,QAAS,MAAA,GAAA,MAAA,CAAO,OAAO,eAAe,CAAA,CAAA;AAAA,OACxC;AAEA,MAAA,MAAM,YAA6B,GAAA;AAAA,QACjC,OAAS,EAAA,eAAA;AAAA,QACT,IAAM,EAAA,MAAA;AAAA,OACR,CAAA;AACA,MAAA,QAAA,CAAS,KAAK,YAAY,CAAA,CAAA;AAAA,KAC5B;AAAA,GACF,CAAA;AAEA,EAAA,MAAA,CAAO,GAAI,CAAA,yBAAA,EAA2B,OAAO,OAAA,EAAS,QAAa,KAAA;AACjE,IAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,KAAK,qBAAsB,CAAA;AAAA,MACjD,UAAA,EAAY,MAAM,IAAA,CAAK,wBAAyB,EAAA;AAAA,MAChD,cAAgB,EAAA,SAAA;AAAA,KACjB,CAAA,CAAA;AAED,IAAA,MAAM,WAAc,GAAA,MAAM,QAAS,CAAA,WAAA,CAAY,OAAS,EAAA;AAAA,MACtD,KAAA,EAAO,CAAC,MAAM,CAAA;AAAA,KACf,CAAA,CAAA;AAGD,IAAA,IAAI,CAAC,IAAA,CAAK,WAAY,CAAA,WAAA,EAAa,MAAM,CAAG,EAAA;AAC1C,MAAA,QAAA,CAAS,MAAO,CAAA,GAAG,CAAE,CAAA,IAAA,CAAK,EAAE,CAAA,CAAA;AAC5B,MAAA,OAAA;AAAA,KACF;AAEA,IAAA,MAAM,IAAO,GAAA,MAAM,QAAS,CAAA,WAAA,CAAY,WAAW,CAAA,CAAA;AAEnD,IAAA,MAAM,UAAc,GAAA,MAAMA,eAAc,CAAA,cAAA,CAAe,KAAK,aAAe,EAAA;AAAA,MACzE,KAAA;AAAA,KACD,CAAA,CAAA;AAED,IAAA,IAAI,CAAC,UAAY,EAAA;AACf,MAAM,MAAA,KAAA,GAAQ,CAAyC,sCAAA,EAAA,IAAA,CAAK,aAAa,CAAA,CAAA,CAAA;AACzE,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA,CAAA;AACjB,MAAA,QAAA,CAAS,MAAO,CAAA,GAAG,CAAE,CAAA,IAAA,CAAK,KAAK,CAAA,CAAA;AAC/B,MAAA,OAAA;AAAA,KACF;AAEA,IAAM,MAAA,QAAA,GAAW,cAAe,CAAA,MAAA,EAAQ,UAAU,CAAA,CAAA;AAElD,IAAA,MAAM,UAAa,GAAA,MAAA;AAAA,MACjB,OAAA,CAAQ,MAAM,UAAc,IAAA,+BAAA;AAAA,KAC9B,CAAA;AAEA,IAAI,IAAA;AACF,MAAA,MAAM,SAAS,MAAM,aAAA,CAAc,QAAU,EAAA,UAAA,EAAY,QAAQG,OAAK,CAAA,CAAA;AACtE,MAAA,QAAA,CAAS,MAAO,CAAA,GAAG,CAAE,CAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAAA,aACzB,GAAU,EAAA;AACjB,MAAA,MAAA,CAAO,KAAM,CAAA,CAAA,kCAAA,EAAqC,GAAI,CAAA,OAAO,CAAE,CAAA,CAAA,CAAA;AAC/D,MAAS,QAAA,CAAA,MAAA,CAAO,GAAG,CAAA,CAAE,IAAK,CAAA;AAAA,QACxB,KAAA,EAAO,CAAqC,kCAAA,EAAA,GAAA,CAAI,OAAO,CAAA,CAAA;AAAA,OACxD,CAAA,CAAA;AACD,MAAA,OAAA;AAAA,KACF;AAAA,GACD,CAAA,CAAA;AAED,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,8CAAA;AAAA,IACA,OAAO,SAAS,QAAa,KAAA;AAC3B,MAAA,MAAM,EAAE,IAAA,EAAM,SAAW,EAAA,IAAA,KAAS,OAAQ,CAAA,MAAA,CAAA;AAC1C,MAAA,MAAM,YAAYG,+BAAmB,CAAA,EAAE,IAAM,EAAA,SAAA,EAAW,MAAM,CAAA,CAAA;AAC9D,MAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,KAAK,qBAAsB,CAAA;AAAA,QACjD,UAAA,EAAY,MAAM,IAAA,CAAK,wBAAyB,EAAA;AAAA,QAChD,cAAgB,EAAA,SAAA;AAAA,OACjB,CAAA,CAAA;AACD,MAAA,MAAM,SAAS,MAAMN,eAAA,CAAc,eAAe,SAAW,EAAA,EAAE,OAAO,CAAA,CAAA;AACtE,MAAA,MAAM,EAAE,oBAAA,EAAyB,GAAA,cAAA,CAAe,MAAM,CAAA,CAAA;AAEtD,MAAA,IAAI,CAAC,MAAQ,EAAA;AACX,QAAO,MAAA,CAAA,IAAA,CAAK,CAAuB,oBAAA,EAAA,SAAS,CAAE,CAAA,CAAA,CAAA;AAC9C,QACG,QAAA,CAAA,MAAA,CAAO,GAAG,CACV,CAAA,IAAA,CAAK,EAAE,KAAO,EAAA,CAAA,oBAAA,EAAuB,SAAS,CAAA,CAAA,EAAI,CAAA,CAAA;AACrD,QAAA,OAAA;AAAA,OACF;AAEA,MAAA,MAAM,aACJ,MAAO,CAAA,QAAA,CAAS,cAAc,oBAAoB,CAAA,EAAG,MAAM,GAAG,CAAA,CAAA;AAEhE,MAAA,MAAM,kBAAkB,MAAM,kBAAA;AAAA,QAC5B,WAAW,CAAC,CAAA;AAAA,QACZ,MAAA;AAAA,QACAG,OAAA;AAAA,OACF,CAAA;AAEA,MAAA,IAAI,CAAC,eAAiB,EAAA;AACpB,QAAA,MAAA,CAAO,MAAM,gCAAgC,CAAA,CAAA;AAC7C,QAAS,QAAA,CAAA,MAAA,CAAO,GAAG,CAAA,CAAE,IAAK,CAAA;AAAA,UACxB,KAAO,EAAA,CAAA,sCAAA,EAAyC,UAAW,CAAA,CAAC,CAAC,CAAA,CAAA;AAAA,SAC9D,CAAA,CAAA;AACD,QAAA,OAAA;AAAA,OACF;AAEA,MAAM,MAAA,GAAA,GAAM,eAAgB,CAAA,UAAA,CAAW,OAAO,CAAA,CAAA;AAE9C,MAAA,MAAM,MAAS,GAAA,MAAM,gBAAiB,CAAA,GAAA,EAAK,MAAM,CAAA,CAAA;AAEjD,MAAM,MAAA,EAAA,GAAK,IAAII,uBAAA,CAAO,WAAY,EAAA,CAAA;AAClC,MAAA,MAAM,GAAM,GAAA,MAAA,CAAO,OAAQ,CAAA,GAAA,CAAI,cAAc,CAAA,CAAA;AAE7C,MAAS,QAAA,CAAA,SAAA,CAAU,cAAgB,EAAA,GAAA,IAAO,EAAE,CAAA,CAAA;AAC5C,MAAAA,uBAAA,CAAO,QAAS,CAAA,MAAA,CAAO,IAAM,EAAA,EAAA,EAAI,CAAO,GAAA,KAAA;AACtC,QAAA,IAAI,GAAK,EAAA;AACP,UAAO,MAAA,CAAA,KAAA,CAAM,CAAG,EAAA,GAAG,CAAE,CAAA,CAAA,CAAA;AACrB,UAAA,QAAA,CAAS,WAAW,GAAG,CAAA,CAAA;AAAA,SACzB;AACA,QAAA,OAAA;AAAA,OACD,CAAA,CAAA;AACD,MAAA,EAAA,CAAG,KAAK,QAAQ,CAAA,CAAA;AAAA,KAClB;AAAA,GACF,CAAA;AACA,EAAA,MAAM,aAAaC,gCAAkB,CAAA,MAAA,CAAO,EAAE,MAAA,EAAQ,QAAQ,CAAA,CAAA;AAC9D,EAAO,MAAA,CAAA,GAAA,CAAI,UAAW,CAAA,KAAA,EAAO,CAAA,CAAA;AAC7B,EAAO,OAAA,MAAA,CAAA;AACT;;AChUO,MAAM,sBAAsBC,oCAAoB,CAAA;AAAA,EACrD,QAAU,EAAA,gBAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,MAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,MAAMA,6BAAa,CAAA,IAAA;AAAA,QACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,UAAUA,6BAAa,CAAA,QAAA;AAAA,OACzB;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,MAAA;AAAA,QACA,MAAA;AAAA,QACA,SAAA;AAAA,QACA,UAAA;AAAA,QACA,IAAA;AAAA,QACA,QAAA;AAAA,QACA,QAAA;AAAA,OACC,EAAA;AACD,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAM,YAAa,CAAA;AAAA,YACjB,MAAA;AAAA,YACA,MAAA;AAAA,YACA,SAAA;AAAA,YACA,IAAA;AAAA,YACA,QAAA;AAAA,YACA,QAAA;AAAA,WACD,CAAA;AAAA,SACH,CAAA;AAAA,OACF;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/lib.ts","../src/queries.ts","../src/api.ts","../src/service/service.ts","../src/service/defaultValues.ts","../src/filters.ts","../src/service/router.ts","../src/config.ts","../src/plugin.ts"],"sourcesContent":["import {\n COMPONENTS_NAME,\n PROJECT_KEY_NAME,\n FILTERS_NAME,\n INCOMING_ISSUES_STATUS,\n} from '@axis-backstage/plugin-jira-dashboard-common';\n\nimport type { ConfigInstance, JiraConfig } from './config';\n\nexport const getAnnotations = (config: JiraConfig) => {\n const prefix = config.annotationPrefix;\n\n const projectKeyAnnotation = `${prefix}/${PROJECT_KEY_NAME}`;\n const componentsAnnotation = `${prefix}/${COMPONENTS_NAME}`;\n const filtersAnnotation = `${prefix}/${FILTERS_NAME}`;\n const incomingIssuesAnnotation = `${prefix}/${INCOMING_ISSUES_STATUS}`;\n\n /* Adding support for Roadie's component annotation */\n const componentRoadieAnnotation = `${prefix}/component`;\n\n return {\n projectKeyAnnotation,\n componentsAnnotation,\n filtersAnnotation,\n incomingIssuesAnnotation,\n componentRoadieAnnotation,\n };\n};\n\nexport interface JiraProject {\n instance: ConfigInstance;\n fullProjectKey: string;\n projectKey: string;\n}\n\n/**\n * Splits a project key \"instance/key\" into a config instance and a project\n * key, falling back to 'default' for unprefixed keys\n */\nexport function splitProjectKey(\n config: JiraConfig,\n fullProjectKey: string,\n): JiraProject {\n const [instance, projectKey] = fullProjectKey.split('/');\n if (!projectKey) {\n // No specific instance specified - use default\n return {\n instance: config.getInstance(),\n fullProjectKey,\n projectKey: instance,\n };\n }\n\n return {\n instance: config.getInstance(instance),\n fullProjectKey,\n projectKey,\n };\n}\n","/**\n * Types for the arguments to the jqlQueryBuilder function.\n *\n * @public\n */\nexport type JqlQueryBuilderArgs = {\n project: string;\n components?: string[];\n query?: string;\n};\n\n/**\n * Creates a jql query string.\n * @public\n */\nexport const jqlQueryBuilder = ({\n project,\n components,\n query,\n}: JqlQueryBuilderArgs) => {\n let jql = `project in (${project})`;\n if (components && components.length > 0) {\n let componentsInclude = '(';\n for (let index = 0; index < components.length; index++) {\n const component = components[index];\n componentsInclude += `'${component}'`;\n // Add either the \",\" separator or close the parentheses.\n if (index === components.length - 1) {\n componentsInclude += ')';\n } else {\n componentsInclude += ',';\n }\n }\n jql += ` AND component in ${componentsInclude}`;\n }\n if (query) {\n jql += ` AND ${query}`;\n }\n return jql;\n};\n","import fetch from 'node-fetch';\nimport {\n Filter,\n Issue,\n Project,\n} from '@axis-backstage/plugin-jira-dashboard-common';\n\nimport type { ConfigInstance } from './config';\nimport { jqlQueryBuilder } from './queries';\nimport type { JiraProject } from './lib';\n\nexport const getProjectInfo = async (\n project: JiraProject,\n): Promise<Project> => {\n const { projectKey, instance } = project;\n const response = await fetch(`${instance.baseUrl}project/${projectKey}`, {\n method: 'GET',\n headers: {\n Authorization: instance.token,\n Accept: 'application/json',\n },\n });\n if (response.status !== 200) {\n throw Error(\n `Request failed with status code ${response.status}: ${response.statusText}`,\n );\n }\n return response.json();\n};\n\nexport const getFilterById = async (\n id: string,\n instance: ConfigInstance,\n): Promise<Filter> => {\n const response = await fetch(`${instance.baseUrl}filter/${id}`, {\n method: 'GET',\n headers: {\n Authorization: instance.token,\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 project: JiraProject,\n components: string[],\n query: string,\n): Promise<Issue[]> => {\n const { projectKey, instance } = project;\n const jql = jqlQueryBuilder({ project: projectKey, components, query });\n const response = await fetch(`${instance.baseUrl}search?jql=${jql}`, {\n method: 'GET',\n headers: {\n Authorization: instance.token,\n Accept: 'application/json',\n },\n }).then(resp => resp.json());\n return response.issues;\n};\n\n/**\n * Options available for the Jira JQL query.\n *\n * @public\n */\nexport type SearchOptions = {\n expand?: string[];\n fields?: string[];\n fieldsByKey?: boolean;\n properties?: string[];\n startAt?: number;\n maxResults?: number;\n validateQuery?: string;\n};\n\n/**\n * Search for Jira issues using JQL.\n *\n * For more information about the available options see the API\n * documentation at:\n * https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-search/#api-rest-api-2-search-post\n *\n * @param config - A Backstage config\n * @param jqlQuery - A string containing the jql query.\n * @param options - Query options that will be passed on to the POST request.\n * @public\n */\nexport const searchJira = async (\n instance: ConfigInstance,\n jqlQuery: string,\n options: SearchOptions,\n): Promise<Issue[]> => {\n const response = await fetch(`${instance.baseUrl}search`, {\n method: 'POST',\n body: JSON.stringify({ jql: jqlQuery, ...options }),\n headers: {\n Authorization: instance.token,\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n }).then(resp => resp.json());\n return response.issues;\n};\n\nexport const getIssuesByComponent = async (\n project: JiraProject,\n componentKey: string,\n): Promise<Issue[]> => {\n const { projectKey, instance } = project;\n\n const jql = jqlQueryBuilder({\n project: projectKey,\n components: [componentKey],\n });\n const response = await fetch(`${instance.baseUrl}search?jql=${jql}`, {\n method: 'GET',\n headers: {\n Authorization: instance.token,\n Accept: 'application/json',\n },\n }).then(resp => resp.json());\n return response.issues;\n};\n\nexport async function getProjectAvatar(url: string, instance: ConfigInstance) {\n return fetch(url, {\n method: 'GET',\n headers: {\n Authorization: instance.token,\n },\n });\n}\n","import { CacheService } from '@backstage/backend-plugin-api';\nimport {\n type Filter,\n Issue,\n type JiraDataResponse,\n type Project,\n} from '@axis-backstage/plugin-jira-dashboard-common';\nimport {\n getFilterById,\n getIssuesByComponent,\n getIssuesByFilter,\n getProjectInfo,\n searchJira,\n SearchOptions,\n} from '../api';\nimport { jqlQueryBuilder } from '../queries';\nimport type { ConfigInstance } from '../config';\nimport { JiraProject } from '../lib';\n\nexport const getProjectResponse = async (\n project: JiraProject,\n cache: CacheService,\n): Promise<Project> => {\n let projectResponse: Project;\n\n projectResponse = (await cache.get(project.fullProjectKey)) as Project;\n\n if (projectResponse) {\n return projectResponse as Project;\n }\n\n try {\n projectResponse = await getProjectInfo(project);\n cache.set(project.fullProjectKey, projectResponse);\n } catch (err: any) {\n if (err.message !== 200) {\n throw Error(\n `Failed to get project info for project key ${project.fullProjectKey} with error: ${err.message}`,\n );\n }\n }\n return projectResponse;\n};\n\nexport const getJqlResponse = async (\n jql: string,\n config: ConfigInstance,\n cache: CacheService,\n searchOptions: SearchOptions,\n): Promise<Issue[]> => {\n let issuesResponse: Issue[];\n\n issuesResponse = (await cache.get(jql)) as Issue[];\n\n if (issuesResponse) {\n return issuesResponse as Issue[];\n }\n\n try {\n issuesResponse = await searchJira(config, jql, searchOptions);\n cache.set(jql, issuesResponse);\n } catch (err: any) {\n if (err.message !== 200) {\n throw Error(\n `Failed to get issues for JQL ${jql} with error: ${err.message}`,\n );\n }\n }\n return issuesResponse;\n};\n\nexport const getUserIssues = async (\n username: string,\n maxResults: number,\n config: ConfigInstance,\n cache: CacheService,\n): Promise<Issue[]> => {\n const jql = `assignee = \"${username}\" AND resolution = Unresolved ORDER BY priority DESC, updated DESC`;\n\n return getJqlResponse(jql, config, cache, {\n fields: [\n 'key',\n 'issuetype',\n 'summary',\n 'status',\n 'priority',\n 'created',\n 'updated',\n ],\n maxResults,\n });\n};\n\nexport const getFiltersFromAnnotations = async (\n annotations: string[],\n config: ConfigInstance,\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 project: JiraProject,\n components: string[],\n filters: Filter[],\n): Promise<JiraDataResponse[]> => {\n return await Promise.all(\n filters.map(async filter => ({\n name: filter.name,\n query: jqlQueryBuilder({\n project: project.projectKey,\n components,\n query: filter.query,\n }),\n type: 'filter',\n issues: await getIssuesByFilter(project, components, filter.query),\n })),\n );\n};\n\nexport const getIssuesFromComponents = async (\n project: JiraProject,\n componentAnnotations: string[],\n): Promise<JiraDataResponse[]> => {\n return await Promise.all(\n componentAnnotations.map(async componentKey => ({\n name: componentKey,\n query: jqlQueryBuilder({\n project: project.projectKey,\n components: [componentKey],\n }),\n type: 'component',\n issues: await getIssuesByComponent(project, componentKey),\n })),\n );\n};\n","export const DEFAULT_TTL = 1000 * 60;\n\nexport const DEFAULT_MAX_RESULTS_USER_ISSUES = 10;\n","import { Filter } from '@axis-backstage/plugin-jira-dashboard-common';\nimport { UserEntity } from '@backstage/catalog-model';\nimport { ConfigInstance } from './config';\n\nconst openFilter: Filter = {\n name: 'Open Issues',\n shortName: 'OPEN',\n query: 'resolution = Unresolved ORDER BY updated DESC',\n};\n\nconst getIncomingFilter = (incomingStatus: string): Filter => ({\n name: 'Incoming Issues',\n shortName: 'INCOMING',\n query: `status = '${incomingStatus}' ORDER BY created ASC`,\n});\n\n/**\n * Get the username value for Jira assignee from the user entity.\n * If the email suffix is defined, the result combines `metadata.name` with the email suffix - otherwise if the user entity has a profile email or the metadata name is used.\n * Otherwise, the user entity name will be used.\n * @param config appConfig configuration\n * @param userEntity user entity instance\n */\nexport const getAssigneUser = (\n instance: ConfigInstance,\n userEntity: UserEntity,\n): string => {\n const emailSuffixConfig = instance.userEmailSuffix;\n\n return emailSuffixConfig\n ? `${userEntity.metadata.name}${emailSuffixConfig}`\n : userEntity.spec?.profile?.email || userEntity.metadata.name;\n};\n\nconst getAssignedToMeFilter = (\n userEntity: UserEntity,\n instance: ConfigInstance,\n): Filter => {\n const email = getAssigneUser(instance, userEntity);\n\n return {\n name: 'Assigned to me',\n shortName: 'ME',\n query: `assignee = \"${email}\" AND resolution = Unresolved ORDER BY updated DESC`,\n };\n};\n\nexport const getDefaultFiltersForUser = (\n instance: ConfigInstance,\n userEntity?: UserEntity,\n incomingStatus?: string,\n): Filter[] => {\n const incomingFilter = getIncomingFilter(incomingStatus ?? 'New');\n\n if (!userEntity) return [openFilter, incomingFilter];\n\n const assigneeToMeFilter = getAssignedToMeFilter(userEntity, instance);\n\n return [openFilter, incomingFilter, assigneeToMeFilter];\n};\n","import express from 'express';\nimport Router from 'express-promise-router';\nimport stream from 'stream';\n\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport { CacheManager } from '@backstage/backend-defaults/cache';\nimport {\n AuthService,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n RootConfigService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { stringifyEntityRef, UserEntity } from '@backstage/catalog-model';\nimport { CatalogClient } from '@backstage/catalog-client';\n\nimport {\n type Filter,\n type JiraResponse,\n type Project,\n} from '@axis-backstage/plugin-jira-dashboard-common';\n\nimport { getAnnotations, splitProjectKey } from '../lib';\nimport {\n getFiltersFromAnnotations,\n getIssuesFromComponents,\n getIssuesFromFilters,\n getProjectResponse,\n getUserIssues,\n} from './service';\nimport { DEFAULT_MAX_RESULTS_USER_ISSUES, DEFAULT_TTL } from './defaultValues';\nimport { getAssigneUser, getDefaultFiltersForUser } from '../filters';\nimport { getProjectAvatar } from '../api';\nimport type { ConfigInstance, JiraConfig } from '../config';\n\nexport interface RouterOptions {\n /**\n * Implementation of Authentication Service\n */\n auth: AuthService;\n /**\n * Implementation of Logger Service\n */\n logger: LoggerService;\n /**\n * Implementation of Config Service\n */\n rootConfig: RootConfigService;\n /**\n * Parsed Jira config\n */\n config: JiraConfig;\n /**\n * Implementation of Discovery Service\n */\n discovery: DiscoveryService;\n /**\n * Implementation of Http Authentication Service\n */\n httpAuth: HttpAuthService;\n /**\n * Implementation of User Info Service\n */\n userInfo: UserInfoService;\n}\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { auth, logger, rootConfig, config, discovery, httpAuth, userInfo } =\n options;\n const catalogClient = new CatalogClient({ discoveryApi: discovery });\n\n const pluginCache =\n CacheManager.fromConfig(rootConfig).forPlugin('jira-dashboard');\n const cache = pluginCache.getClient({ defaultTtl: DEFAULT_TTL });\n logger.info('Initializing Jira Dashboard backend');\n\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n logger.info('PONG!');\n response.json({ status: 'ok' });\n });\n\n router.get(\n '/dashboards/by-entity-ref/:kind/:namespace/:name',\n async (request, response) => {\n const { kind, namespace, name } = request.params;\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await catalogClient.getEntityByRef(entityRef, { token });\n const {\n projectKeyAnnotation,\n componentsAnnotation,\n filtersAnnotation,\n incomingIssuesAnnotation,\n componentRoadieAnnotation,\n } = getAnnotations(config);\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 fullProjectKeys =\n entity.metadata.annotations?.[projectKeyAnnotation]?.split(',')!;\n\n if (!fullProjectKeys) {\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 const projects = fullProjectKeys.map(fullProjectKey =>\n splitProjectKey(config, fullProjectKey),\n );\n\n let projectResponse;\n\n try {\n projectResponse = await getProjectResponse(projects[0], cache);\n } catch (err: any) {\n logger.error(\n `Could not find Jira project ${projects[0].fullProjectKey}: ${err.message}`,\n );\n response.status(404).json({\n error: `No Jira project found with key ${projects[0].projectKey}`,\n });\n return;\n }\n\n let userEntity: UserEntity | undefined;\n\n try {\n const credentials = await httpAuth.credentials(request, {\n allow: ['user'],\n });\n const userIdentity = credentials.principal.userEntityRef;\n\n userEntity = (await catalogClient.getEntityByRef(userIdentity, {\n token,\n })) as UserEntity;\n } catch (err) {\n logger.warn('Could not find user identity');\n }\n\n let filters: Filter[] = [];\n\n const incomingStatus =\n entity.metadata.annotations?.[incomingIssuesAnnotation];\n\n filters = getDefaultFiltersForUser(\n projects[0].instance,\n userEntity,\n incomingStatus,\n );\n\n const customFilterAnnotations =\n entity.metadata.annotations?.[filtersAnnotation]?.split(',')!;\n\n if (customFilterAnnotations) {\n filters.push(\n ...(await getFiltersFromAnnotations(\n customFilterAnnotations,\n projects[0].instance,\n )),\n );\n }\n\n let components =\n entity.metadata.annotations?.[componentsAnnotation]?.split(',') ?? [];\n let issues = await getIssuesFromFilters(projects[0], components, filters);\n\n /* Adding support for Roadie's component annotation */\n components = components.concat(\n entity.metadata.annotations?.[componentRoadieAnnotation]?.split(',') ??\n [],\n );\n\n if (components) {\n const componentIssues = await getIssuesFromComponents(\n projects[0],\n components,\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('/dashboards/user-issues', async (request, response) => {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n\n const credentials = await httpAuth.credentials(request, {\n allow: ['user'],\n });\n\n // we ignore guest and service users, no issues in response\n if (!auth.isPrincipal(credentials, 'user')) {\n response.status(200).json([]);\n return;\n }\n\n const info = await userInfo.getUserInfo(credentials);\n\n const userEntity = (await catalogClient.getEntityByRef(info.userEntityRef, {\n token,\n })) as UserEntity;\n\n if (!userEntity) {\n const error = `User entity cannot be determined from ${info.userEntityRef}`;\n logger.info(error);\n response.status(400).json(error);\n return;\n }\n\n const getUserIssuesForInstance = async (instance: ConfigInstance) => {\n const username = getAssigneUser(instance, userEntity);\n\n const maxResults = Number(\n request.query.maxResults || DEFAULT_MAX_RESULTS_USER_ISSUES,\n );\n\n try {\n const issues = await getUserIssues(\n username,\n maxResults,\n instance,\n cache,\n );\n return { issues, error: undefined };\n } catch (error: any) {\n return { error };\n }\n };\n\n const issuesList = await Promise.all(\n config\n .getInstances()\n .map(instanceName =>\n getUserIssuesForInstance(config.getInstance(instanceName)),\n ),\n );\n\n const issues = issuesList.flatMap(list => list.issues ?? []);\n const errors = issuesList\n .flatMap(list => list.error)\n .filter((v): v is NonNullable<typeof v> => !!v);\n\n if (issues.length > 0 || errors.length === 0) {\n response.status(200).json(issues);\n } else {\n const messages =\n errors.length > 1\n ? `\\n ${errors.map(err => err.message).join('\\n ')}`\n : ` ${errors[0].message}`;\n\n logger.error(`Error during getting user issues:${messages}`);\n response.status(503).json({\n error: `Error during getting user issues:${messages}`,\n });\n }\n });\n\n router.get(\n '/avatar/by-entity-ref/:kind/:namespace/:name',\n async (request, response) => {\n const { kind, namespace, name } = request.params;\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await catalogClient.getEntityByRef(entityRef, { token });\n const { projectKeyAnnotation } = getAnnotations(config);\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 fullProjectKeys =\n entity.metadata.annotations?.[projectKeyAnnotation]?.split(',')!;\n\n if (!fullProjectKeys) {\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 const projects = fullProjectKeys.map(fullProjectKey =>\n splitProjectKey(config, fullProjectKey),\n );\n\n const projectResponse = await getProjectResponse(projects[0], 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 ${projects[0].projectKey}`,\n });\n return;\n }\n\n const url = projectResponse.avatarUrls['48x48'];\n\n const avatar = await getProjectAvatar(url, projects[0].instance);\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 );\n\n const middleware = MiddlewareFactory.create({ logger, config: rootConfig });\n\n router.use(middleware.error());\n return router;\n}\n","import { ConflictError, ServiceUnavailableError } from '@backstage/errors';\nimport { RootConfigService } from '@backstage/backend-plugin-api';\n\n/**\n * The configuration of a Jira instance\n *\n * @public\n */\nexport type ConfigInstance = {\n token: string;\n baseUrl: string;\n userEmailSuffix?: string;\n};\n\nconst JIRA_CONFIG_BASE_URL = 'baseUrl';\nconst JIRA_CONFIG_TOKEN = 'token';\nconst JIRA_CONFIG_USER_EMAIL_SUFFIX = 'userEmailSuffix';\nconst JIRA_CONFIG_ANNOTATION = 'annotationPrefix';\n\nexport class JiraConfig {\n private instances: Record<string, ConfigInstance> = {};\n public readonly annotationPrefix: string;\n\n private constructor(config: RootConfigService) {\n const jira = config.getConfig('jiraDashboard');\n\n this.annotationPrefix =\n jira.getOptionalString(JIRA_CONFIG_ANNOTATION) ?? 'jira.com';\n\n const instances = jira.getOptionalConfigArray('instances');\n if (instances) {\n // Multiple instances form\n instances.forEach(inst => {\n const name = inst.getString('name');\n if (Object.getOwnPropertyNames(this.instances).includes(name)) {\n throw new ConflictError(\n `Duplicate jiraDashboard instances: '${name}'`,\n );\n }\n\n this.instances[name] = {\n token: inst.getString(JIRA_CONFIG_TOKEN),\n baseUrl: inst.getString(JIRA_CONFIG_BASE_URL),\n userEmailSuffix: inst.getOptionalString(\n JIRA_CONFIG_USER_EMAIL_SUFFIX,\n ),\n };\n });\n } else {\n // Default form\n this.instances.default = {\n token: jira.getString(JIRA_CONFIG_TOKEN),\n baseUrl: jira.getString(JIRA_CONFIG_BASE_URL),\n userEmailSuffix: jira.getOptionalString(JIRA_CONFIG_USER_EMAIL_SUFFIX),\n };\n }\n }\n\n public static fromConfig(config: RootConfigService): JiraConfig {\n return new JiraConfig(config);\n }\n\n private forInstance(instanceName: string) {\n const instance = this.instances[instanceName];\n if (!instance) {\n throw new ServiceUnavailableError(\n `No such jira instance '${instanceName}'`,\n );\n }\n return instance;\n }\n\n getInstances() {\n return Object.getOwnPropertyNames(this.instances);\n }\n\n getInstance(instanceName?: string): ConfigInstance {\n return this.forInstance(instanceName ?? 'default');\n }\n\n resolveJiraBaseUrl(instanceName: string): string {\n const instance = this.forInstance(instanceName);\n return instance.baseUrl;\n }\n\n resolveJiraToken(instanceName: string): string {\n const instance = this.forInstance(instanceName);\n return instance.token;\n }\n\n resolveUserEmailSuffix(instanceName: string): string | undefined {\n const instance = this.forInstance(instanceName);\n return instance.userEmailSuffix;\n }\n}\n","import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\nimport { JiraConfig } from './config';\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 auth: coreServices.auth,\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n rootConfig: coreServices.rootConfig,\n discovery: coreServices.discovery,\n httpAuth: coreServices.httpAuth,\n userInfo: coreServices.userInfo,\n },\n async init({\n auth,\n httpRouter,\n logger,\n rootConfig,\n discovery,\n httpAuth,\n userInfo,\n }) {\n httpRouter.use(\n await createRouter({\n auth,\n logger,\n rootConfig,\n config: JiraConfig.fromConfig(rootConfig),\n discovery,\n httpAuth,\n userInfo,\n }),\n );\n httpRouter.addAuthPolicy({\n path: '/health',\n allow: 'unauthenticated',\n });\n },\n });\n },\n});\n"],"names":["PROJECT_KEY_NAME","COMPONENTS_NAME","FILTERS_NAME","INCOMING_ISSUES_STATUS","fetch","catalogClient","CatalogClient","CacheManager","cache","Router","express","stringifyEntityRef","issues","stream","MiddlewareFactory","ConflictError","ServiceUnavailableError","createBackendPlugin","coreServices"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,61 +1,23 @@
|
|
|
1
1
|
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
-
import { LoggerService, RootConfigService, DiscoveryService, IdentityService, TokenManagerService, AuthService, HttpAuthService, UserInfoService } from '@backstage/backend-plugin-api';
|
|
3
|
-
import express from 'express';
|
|
4
2
|
import { Issue } from '@axis-backstage/plugin-jira-dashboard-common';
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
|
-
*
|
|
8
|
-
* @deprecated Please migrate to the new backend system as this will be removed in the future.
|
|
9
|
-
* @public
|
|
10
|
-
*/
|
|
11
|
-
interface RouterOptions {
|
|
12
|
-
/**
|
|
13
|
-
* Implementation of Winston logger
|
|
14
|
-
*/
|
|
15
|
-
logger: LoggerService;
|
|
16
|
-
/**
|
|
17
|
-
* Backstage config object
|
|
18
|
-
*/
|
|
19
|
-
config: RootConfigService;
|
|
20
|
-
/**
|
|
21
|
-
* Backstage discovery api instance
|
|
22
|
-
*/
|
|
23
|
-
discovery: DiscoveryService;
|
|
24
|
-
/**
|
|
25
|
-
* Backstage identity api instance
|
|
26
|
-
*/
|
|
27
|
-
identity?: IdentityService;
|
|
28
|
-
/**
|
|
29
|
-
* Backstage token manager instance
|
|
30
|
-
*/
|
|
31
|
-
tokenManager?: TokenManagerService;
|
|
32
|
-
/**
|
|
33
|
-
* Backstage auth service
|
|
34
|
-
*/
|
|
35
|
-
auth?: AuthService;
|
|
36
|
-
/**
|
|
37
|
-
* Backstage httpAuth service
|
|
38
|
-
*/
|
|
39
|
-
httpAuth?: HttpAuthService;
|
|
40
|
-
/**
|
|
41
|
-
* Backstage userInfo service
|
|
42
|
-
*/
|
|
43
|
-
userInfo: UserInfoService;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Constructs a jira dashboard router.
|
|
5
|
+
* The Jira Dashboard backend plugin.
|
|
47
6
|
*
|
|
48
|
-
* @deprecated Please migrate to the new backend system as this will be removed in the future.
|
|
49
7
|
* @public
|
|
50
8
|
*/
|
|
51
|
-
declare
|
|
9
|
+
declare const jiraDashboardPlugin: _backstage_backend_plugin_api.BackendFeatureCompat;
|
|
52
10
|
|
|
53
11
|
/**
|
|
54
|
-
* The
|
|
12
|
+
* The configuration of a Jira instance
|
|
55
13
|
*
|
|
56
14
|
* @public
|
|
57
15
|
*/
|
|
58
|
-
|
|
16
|
+
type ConfigInstance = {
|
|
17
|
+
token: string;
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
userEmailSuffix?: string;
|
|
20
|
+
};
|
|
59
21
|
|
|
60
22
|
/**
|
|
61
23
|
* Options available for the Jira JQL query.
|
|
@@ -81,10 +43,9 @@ type SearchOptions = {
|
|
|
81
43
|
* @param config - A Backstage config
|
|
82
44
|
* @param jqlQuery - A string containing the jql query.
|
|
83
45
|
* @param options - Query options that will be passed on to the POST request.
|
|
84
|
-
*
|
|
85
46
|
* @public
|
|
86
47
|
*/
|
|
87
|
-
declare const searchJira: (
|
|
48
|
+
declare const searchJira: (instance: ConfigInstance, jqlQuery: string, options: SearchOptions) => Promise<Issue[]>;
|
|
88
49
|
|
|
89
50
|
/**
|
|
90
51
|
* Types for the arguments to the jqlQueryBuilder function.
|
|
@@ -98,9 +59,8 @@ type JqlQueryBuilderArgs = {
|
|
|
98
59
|
};
|
|
99
60
|
/**
|
|
100
61
|
* Creates a jql query string.
|
|
101
|
-
*
|
|
102
62
|
* @public
|
|
103
63
|
*/
|
|
104
64
|
declare const jqlQueryBuilder: ({ project, components, query, }: JqlQueryBuilderArgs) => string;
|
|
105
65
|
|
|
106
|
-
export { type
|
|
66
|
+
export { type ConfigInstance, type JqlQueryBuilderArgs, type SearchOptions, jiraDashboardPlugin as default, jqlQueryBuilder, searchJira };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axis-backstage/plugin-jira-dashboard-backend",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -29,14 +29,13 @@
|
|
|
29
29
|
"postpack": "backstage-cli package postpack"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@axis-backstage/plugin-jira-dashboard-common": "^1.
|
|
32
|
+
"@axis-backstage/plugin-jira-dashboard-common": "^1.8.0",
|
|
33
33
|
"@backstage/backend-common": "^0.24.0",
|
|
34
34
|
"@backstage/backend-defaults": "^0.4.3",
|
|
35
35
|
"@backstage/backend-plugin-api": "^0.8.0",
|
|
36
36
|
"@backstage/catalog-client": "^1.6.6",
|
|
37
37
|
"@backstage/catalog-model": "^1.6.0",
|
|
38
|
-
"@backstage/
|
|
39
|
-
"@types/express": "*",
|
|
38
|
+
"@backstage/errors": "^1.2.4",
|
|
40
39
|
"express": "^4.17.1",
|
|
41
40
|
"express-promise-router": "^4.1.0",
|
|
42
41
|
"node-fetch": "^2.6.7"
|
|
@@ -44,6 +43,9 @@
|
|
|
44
43
|
"devDependencies": {
|
|
45
44
|
"@backstage/backend-test-utils": "^0.5.0",
|
|
46
45
|
"@backstage/cli": "^0.27.0",
|
|
46
|
+
"@backstage/plugin-auth-backend": "^0.23.0",
|
|
47
|
+
"@backstage/plugin-auth-backend-module-guest-provider": "^0.2.0",
|
|
48
|
+
"@types/express": "*",
|
|
47
49
|
"@types/supertest": "^2.0.12",
|
|
48
50
|
"msw": "^1.0.0",
|
|
49
51
|
"supertest": "^6.2.4"
|