@axis-backstage/plugin-jira-dashboard-backend 4.9.1 → 6.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # @axis-backstage/plugin-jira-dashboard-backend
2
2
 
3
+ ## 6.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 5f6da06: **Added configurable cache TTL (Time-To-Live) for Jira API responses**
8
+
9
+ This release introduces comprehensive cache TTL configuration support for the Jira Dashboard backend plugin:
10
+
11
+ ## Configuration Examples
12
+
13
+ ```yaml
14
+ # Single instance with custom TTL
15
+ jira:
16
+ baseUrl: 'https://your-jira-instance.com'
17
+ cacheTtl: '30m' # Cache for 30 minutes
18
+
19
+ # Multiple instances with different TTL settings
20
+ jira:
21
+ instances:
22
+ - name: 'production'
23
+ baseUrl: 'https://prod-jira.com'
24
+ cacheTtl: '2h' # Cache for 2 hours
25
+ ```
26
+
27
+ ## Breaking Changes
28
+
29
+ - A default cache TTL of 1 hour is now applied to all cached responses
30
+ - This may change caching behavior for deployments relying on indefinite cache duration
31
+
32
+ ## 5.0.0
33
+
34
+ ### Major Changes
35
+
36
+ - f8fb7ba: Added support for Jira Cloud API V3
37
+
38
+ Added useApiV3 configuration option to enable Jira REST API v3 support. The "/api/2/search"
39
+ API used by the Jira Data Center is deprecated and may not be available in Jira Cloud.
40
+
41
+ Configuration Changes:
42
+
43
+ Added optional useApiV3 boolean field to the Jira dashboard configuration
44
+ When useApiV3: true, API calls use the /search/jql endpoint instead of /search
45
+ When useApiV3: false or not specified, continues to use the existing v2 /search endpoint
46
+
47
+ Example configuration for Jira Cloud to use the v3 Api.
48
+
49
+ ```
50
+ jiraDashboard:
51
+ baseUrl: 'https://your-jira.atlassian.net/rest/api/3/'
52
+ token: 'your-token'
53
+ useApiV3: true
54
+ ```
55
+
3
56
  ## 4.9.1
4
57
 
5
58
  ### Patch Changes
package/README.md CHANGED
@@ -39,7 +39,7 @@ jiraDashboard:
39
39
  - `JIRA_API_URL`: Optional url for the Jira API, if different from the base url, including the API version. Necessary when using scoped API tokens with Jira Cloud. For instance: https://api.atlassian.com/ex/jira/7c9c39d8-d07d-43bd-924b-a82397d47f45/rest/api/2/
40
40
  - The headers field can be used to add HTTP headers that will be added to the API requests.
41
41
  - `JIRA_EMAIL_SUFFIX`: 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.
42
- - `JIRA_ANNOTATION_PREFIX`: Optional annotation prefix for retrieving a custom annotation. Defaut value is jira.com. If you want to configure the plugin to be compatible with [Roadie's Backstage Jira Plugin](https://roadie.io/backstage/plugins/jira/), use the following annotation prefix:
42
+ - `JIRA_ANNOTATION_PREFIX`: Optional annotation prefix for retrieving a custom annotation. Default value is jira.com. If you want to configure the plugin to be compatible with [Roadie's Backstage Jira Plugin](https://roadie.io/backstage/plugins/jira/), use the following annotation prefix:
43
43
 
44
44
  ```yaml
45
45
  jiraDashboard:
@@ -47,6 +47,81 @@ jiraDashboard:
47
47
  annotationPrefix: jira
48
48
  ```
49
49
 
50
+ ### Jira API Version Configuration
51
+
52
+ The plugin supports both Jira REST API v2 (default) and v3. You can enable API v3 by setting the `useApiV3` configuration option:
53
+
54
+ ```yaml
55
+ jiraDashboard:
56
+ token: ${JIRA_TOKEN}
57
+ baseUrl: ${JIRA_BASE_URL}
58
+ useApiV3: true # Enable Jira API v3 (defaults to false)
59
+ ```
60
+
61
+ #### API Version Differences:
62
+
63
+ - **API v2 (default)**: Uses the `/search` endpoint for JQL queries
64
+ - **API v3**: Uses the `/search/jql` endpoint for JQL queries
65
+
66
+ When `useApiV3` is set to `true`, the plugin will use the v3 API endpoints. When `useApiV3` is `false` or not specified, the plugin will continue to use the v2 API endpoints for backward compatibility.
67
+
68
+ #### Example with API v3:
69
+
70
+ ```yaml
71
+ jiraDashboard:
72
+ token: Bearer <your-token>
73
+ baseUrl: https://your-domain.atlassian.net/rest/api/3/
74
+ useApiV3: true
75
+ userEmailSuffix: @your-company.com
76
+ ```
77
+
78
+ **Note**: When using API v3, make sure your `baseUrl` also points to the v3 endpoint (e.g., `/rest/api/3/` instead of `/rest/api/2/`).
79
+
80
+ ### Cache Configuration
81
+
82
+ The plugin includes configurable caching to improve performance by reducing API calls to Jira. You can configure the cache TTL (Time To Live) for each Jira instance:
83
+
84
+ ```yaml
85
+ jiraDashboard:
86
+ token: ${JIRA_TOKEN}
87
+ baseUrl: ${JIRA_BASE_URL}
88
+ cacheTtl: 30m # Cache for 30 minutes (defaults to 1h if not specified)
89
+ ```
90
+
91
+ #### Supported TTL Formats:
92
+
93
+ The `cacheTtl` option accepts human-readable duration formats as a string:
94
+
95
+ - `30s` - 30 seconds
96
+ - `10m` - 10 minutes
97
+ - `1h` - 1 hour
98
+ - `2h30m` - 2 hours and 30 minutes
99
+ - `1d` - 1 day
100
+
101
+ Or an object like:
102
+
103
+ ```yaml
104
+ jiraDashboard:
105
+ cacheTtl:
106
+ hours: 2
107
+ ```
108
+
109
+ #### Default Cache Behavior:
110
+
111
+ - **Default TTL**: 1 hour if not specified
112
+ - **Cache Scope**: Project information and JQL query results are cached per Jira instance
113
+ - **Cache Keys**: Unique per project key and JQL query to ensure data accuracy
114
+
115
+ #### Example with custom cache TTL:
116
+
117
+ ```yaml
118
+ jiraDashboard:
119
+ token: Bearer <your-token>
120
+ baseUrl: https://your-domain.atlassian.net/rest/api/2/
121
+ cacheTtl: 15m # Cache responses for 15 minutes
122
+ userEmailSuffix: @your-company.com
123
+ ```
124
+
50
125
  ### Multiple Jira instances
51
126
 
52
127
  In case multiple Jira instances are being used, the configuration can be written on the form:
@@ -61,15 +136,21 @@ jiraDashboard:
61
136
  apiUrl: ${JIRA_API_URL} # Optional
62
137
  headers: {} # Optional
63
138
  userEmailSuffix: ${JIRA_EMAIL_SUFFIX} # Optional
139
+ useApiV3: false # Optional - defaults to false
140
+ cacheTtl: 1h # Optional - defaults to 1 hour
64
141
  - name: separate-jira-instance
65
142
  token: ${JIRA_TOKEN_SEPARATE}
66
143
  baseUrl: ${JIRA_BASE_URL_SEPARATE}
67
144
  apiUrl: ${JIRA_API_URL_SEPARATE} # Optional
68
145
  headers: {} # Optional
69
146
  userEmailSuffix: ${JIRA_EMAIL_SUFFIX_SEPARATE} # Optional
147
+ useApiV3: true # Optional - enable API v3 for this instance
148
+ cacheTtl: 30m # Optional - cache for 30 minutes for this instance
70
149
  ```
71
150
 
72
- 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:
151
+ Each instance can have its own `useApiV3` and `cacheTtl` settings, allowing you to mix v2 and v3 API usage and customize cache durations across different Jira instances.
152
+
153
+ In entity yamls that don't specify an instance, the one called `"default"` will be used. To specify another instance, prefix the project key with `instance-name/` such as:
73
154
 
74
155
  ```yaml
75
156
  metadata:
@@ -101,7 +182,7 @@ jiraDashboard:
101
182
 
102
183
  #### Authentication examples and trouble shooting
103
184
 
104
- Either "Basic Auth" or "Personal Acccess Tokens" can be used.
185
+ Either "Basic Auth" or "Personal Access Tokens" can be used.
105
186
 
106
187
  This plugin will directly take the content of the "jiraDashboard.token" config string and
107
188
  use as the "Authorization" header in all Jira REST API calls.
@@ -127,9 +208,9 @@ jiraDashboard:
127
208
  userEmailSuffix: ${JIRA_EMAIL_SUFFIX}
128
209
  ```
129
210
 
130
- ##### Personal Acccess Tokens example
211
+ ##### Personal Access Tokens example
131
212
 
132
- See the [Personal Acccess Tokens](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) documentation how to use and create
213
+ See the [Personal Access Tokens](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) documentation how to use and create
133
214
  a PAT. The generated token can be directly used, there is no need to encode it. Using
134
215
  `curl` with a PAT.
135
216
 
@@ -150,6 +231,27 @@ jiraDashboard:
150
231
  userEmailSuffix: ${JIRA_EMAIL_SUFFIX}
151
232
  ```
152
233
 
234
+ Authentication with Jira Cloud may require configuring basic auth like this.
235
+ Note that the email and token must be base64 encoded. This what the configuration
236
+ will look like before the encoding step.
237
+
238
+ ```yaml
239
+ jiraDashboard:
240
+ token: Basic email@example.com:pat_token
241
+ baseUrl: https://your-domain.atlassian.net/rest/api/2/
242
+ userEmailSuffix: ${JIRA_EMAIL_SUFFIX}
243
+ ```
244
+
245
+ And this is the actual Backstage configuration where the "email@example.com:pat_token" string
246
+ has been base64 encoded.
247
+
248
+ ```yaml
249
+ jiraDashboard:
250
+ token: Basic ZW1haWxAZXhhbXBsZS5jb206cGF0X3Rva2Vu
251
+ baseUrl: https://your-domain.atlassian.net/rest/api/2/
252
+ userEmailSuffix: ${JIRA_EMAIL_SUFFIX}
253
+ ```
254
+
153
255
  ##### Using Scoped API Tokens with Atlassian Cloud
154
256
 
155
257
  If you want to use scoped API tokens, which allow limiting the permission of the token, you need to call a different Atlassian API endpoint and use a unique `apiUrl` ([see the documentation here](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/#Create-an-API-token-with-scopes)). You will still need to enter the `baseUrl` as well, which should point to your Jira instance (including the API version information).
package/config.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { HumanDuration } from '@backstage/types';
2
+
1
3
  export interface Config {
2
4
  /**
3
5
  * Configuration options for the Jira Dashboard plugin
@@ -34,7 +36,7 @@ export interface Config {
34
36
  userEmailSuffix?: string;
35
37
 
36
38
  /**
37
- * Optional annotation prefix for retrieving a custom annotation. Defaut value is jira.com
39
+ * Optional annotation prefix for retrieving a custom annotation. Default value is jira.com
38
40
  * @visibility frontend
39
41
  */
40
42
  annotationPrefix?: string;
@@ -48,12 +50,22 @@ export interface Config {
48
50
  shortName: string;
49
51
  }[];
50
52
 
53
+ /**
54
+ * Whether to use Jira API v3. Defaults to false for backward compatibility.
55
+ */
56
+ useApiV3?: boolean;
57
+
58
+ /**
59
+ * Optional cache TTL, defaults to 1 hour.
60
+ */
61
+ cacheTtl?: HumanDuration | string;
62
+
51
63
  // Type helper
52
64
  instances?: never;
53
65
  }
54
66
  | {
55
67
  /**
56
- * Optional annotation prefix for retrieving a custom annotation. Defaut value is jira.com
68
+ * Optional annotation prefix for retrieving a custom annotation. Default value is jira.com
57
69
  * @visibility frontend
58
70
  */
59
71
  annotationPrefix?: string;
@@ -101,6 +113,16 @@ export interface Config {
101
113
  query: string;
102
114
  shortName: string;
103
115
  }[];
116
+
117
+ /**
118
+ * Whether to use Jira API v3. Defaults to false for backward compatibility.
119
+ */
120
+ useApiV3?: boolean;
121
+
122
+ /**
123
+ * Optional cache TTL, defaults to 1 hour.
124
+ */
125
+ cacheTtl?: HumanDuration | string;
104
126
  }[];
105
127
  };
106
128
  }
package/dist/api.cjs.js CHANGED
@@ -47,21 +47,33 @@ const getFilterById = async (id, instance) => {
47
47
  const jsonResponse = await response.json();
48
48
  return { name: jsonResponse.name, query: jsonResponse.jql };
49
49
  };
50
+ const getQueryUrl = (instance, jql) => {
51
+ const baseUrl = lib.getApiUrl(instance);
52
+ if (instance.useApiV3) {
53
+ const baseUrlWithEndpoint2 = `${baseUrl}search/jql`;
54
+ if (jql) {
55
+ return `${baseUrlWithEndpoint2}?jql=${jql}&fields=*all`;
56
+ }
57
+ return baseUrlWithEndpoint2;
58
+ }
59
+ const baseUrlWithEndpoint = `${baseUrl}search`;
60
+ if (jql) {
61
+ return `${baseUrlWithEndpoint}?jql=${jql}`;
62
+ }
63
+ return baseUrlWithEndpoint;
64
+ };
50
65
  const getIssuesByFilter = async (projects, components, query) => {
51
66
  const issues = [];
52
67
  for (const project of projects) {
53
68
  const { projectKey, instance } = project;
54
69
  const jql = queries.jqlQueryBuilder({ project: [projectKey], components, query });
55
- const response = await callApi(
56
- instance,
57
- `${lib.getApiUrl(instance)}search?jql=${jql}`,
58
- {
59
- method: "GET",
60
- headers: {
61
- Accept: "application/json"
62
- }
70
+ const queryUrl = getQueryUrl(instance, jql);
71
+ const response = await callApi(instance, queryUrl, {
72
+ method: "GET",
73
+ headers: {
74
+ Accept: "application/json"
63
75
  }
64
- ).then((resp) => resp.json()).catch(() => null);
76
+ }).then((resp) => resp.json()).catch(() => null);
65
77
  if (response?.errorMessages) {
66
78
  throw Error(
67
79
  `JQL returned Error: JQL - ${jql} with error: ${response?.errorMessages?.[0]}`
@@ -75,9 +87,11 @@ const getIssuesByFilter = async (projects, components, query) => {
75
87
  return issues;
76
88
  };
77
89
  const searchJira = async (instance, jqlQuery, options) => {
78
- const response = await callApi(instance, `${lib.getApiUrl(instance)}search`, {
90
+ const queryUrl = getQueryUrl(instance);
91
+ const requestBody = { jql: jqlQuery, ...options };
92
+ const response = await callApi(instance, queryUrl, {
79
93
  method: "POST",
80
- body: JSON.stringify({ jql: jqlQuery, ...options }),
94
+ body: JSON.stringify(requestBody),
81
95
  headers: {
82
96
  Accept: "application/json",
83
97
  "Content-Type": "application/json"
@@ -103,16 +117,13 @@ const getIssuesByComponent = async (projects, componentKeys, query) => {
103
117
  });
104
118
  const { instance } = projects[0];
105
119
  try {
106
- const response = await callApi(
107
- instance,
108
- `${lib.getApiUrl(instance)}search?jql=${jql}`,
109
- {
110
- method: "GET",
111
- headers: {
112
- Accept: "application/json"
113
- }
120
+ const queryUrl = getQueryUrl(instance, jql);
121
+ const response = await callApi(instance, queryUrl, {
122
+ method: "GET",
123
+ headers: {
124
+ Accept: "application/json"
114
125
  }
115
- ).then((resp) => resp.json());
126
+ }).then((resp) => resp.json());
116
127
  if (!response.issues || response.issues.length === 0) {
117
128
  return [];
118
129
  }
@@ -144,5 +155,6 @@ exports.getIssuesByComponent = getIssuesByComponent;
144
155
  exports.getIssuesByFilter = getIssuesByFilter;
145
156
  exports.getProjectAvatar = getProjectAvatar;
146
157
  exports.getProjectInfo = getProjectInfo;
158
+ exports.getQueryUrl = getQueryUrl;
147
159
  exports.searchJira = searchJira;
148
160
  //# sourceMappingURL=api.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.cjs.js","sources":["../src/api.ts"],"sourcesContent":["import fetch, { RequestInit } from 'node-fetch';\nimport {\n Filter,\n Issue,\n Project,\n JiraQueryResults,\n} from '@axis-backstage/plugin-jira-dashboard-common';\n\nimport type { ConfigInstance } from './config';\nimport { jqlQueryBuilder } from './queries';\nimport type { JiraProject } from './lib';\nimport { getApiUrl, replaceProjectApiUrl, replaceIssuesApiUrl } from './lib';\nimport { ResponseError } from '@backstage/errors';\n\nexport const getProjectInfo = async (\n project: JiraProject,\n): Promise<Project> => {\n const { projectKey, instance } = project;\n const response = await callApi(\n instance,\n `${getApiUrl(instance)}project/${projectKey}`,\n {\n method: 'GET',\n headers: {\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\n const projectResponse = await response.json();\n replaceProjectApiUrl(project.instance, projectResponse);\n return projectResponse;\n};\n\nexport const getFilterById = async (\n id: string,\n instance: ConfigInstance,\n): Promise<Filter> => {\n const response = await callApi(\n instance,\n `${getApiUrl(instance)}filter/${id}`,\n {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\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 projects: JiraProject[],\n components: string[],\n query: string,\n): Promise<Issue[]> => {\n const issues: Issue[] = [];\n for (const project of projects) {\n const { projectKey, instance } = project;\n const jql = jqlQueryBuilder({ project: [projectKey], components, query });\n const response = await callApi(\n instance,\n `${getApiUrl(instance)}search?jql=${jql}`,\n {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n },\n )\n .then(resp => resp.json())\n .catch(() => null);\n if (response?.errorMessages) {\n throw Error(\n `JQL returned Error: JQL - ${jql} with error: ${response?.errorMessages?.[0]}`,\n );\n }\n if (response?.issues) {\n replaceIssuesApiUrl(project.instance, response.issues);\n issues.push(...response.issues);\n }\n }\n\n return 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 * Asynchronously searches for Jira issues using JQL.\n *\n * For more information about the available options, see the API 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<JiraQueryResults> => {\n const response = await callApi(instance, `${getApiUrl(instance)}search`, {\n method: 'POST',\n body: JSON.stringify({ jql: jqlQuery, ...options }),\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n const jsonResponse = (await response.json()) as JiraQueryResults;\n replaceIssuesApiUrl(instance, jsonResponse.issues);\n return jsonResponse;\n};\n\nexport const getIssuesByComponent = async (\n projects: JiraProject[],\n componentKeys: string,\n query?: string,\n): Promise<Issue[]> => {\n // Return an empty array if no projects are provided\n if (projects.length === 0) {\n return [];\n }\n\n const projectKeys = projects.map(project => project.projectKey);\n const components = componentKeys\n .split(',')\n .map(component => component.trim());\n\n const jql = jqlQueryBuilder({\n project: projectKeys,\n components,\n query,\n });\n\n const { instance } = projects[0];\n\n try {\n const response = await callApi(\n instance,\n `${getApiUrl(instance)}search?jql=${jql}`,\n {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n },\n ).then(resp => resp.json());\n\n if (!response.issues || response.issues.length === 0) {\n return [];\n }\n\n replaceIssuesApiUrl(instance, response.issues);\n return response.issues;\n } catch (error: any) {\n if (error.message.includes(\"does not exist for the field 'project'\")) {\n return [];\n }\n throw error;\n }\n};\nexport async function getProjectAvatar(url: string, instance: ConfigInstance) {\n return callApi(instance, url);\n}\n\n/**\n * Call the Jira API using fetch.\n *\n * This function injects the auth token and custom headers.\n *\n * @public\n */\nexport async function callApi(\n instance: ConfigInstance,\n url: string,\n init?: RequestInit,\n) {\n const requestInit = init ?? { method: 'GET' };\n\n // Inject custom headers from config, Authorization token and headers from the\n // request\n requestInit.headers = {\n ...instance.headers,\n Authorization: instance.token,\n ...requestInit.headers,\n };\n return fetch(url, requestInit);\n}\n"],"names":["getApiUrl","replaceProjectApiUrl","jqlQueryBuilder","replaceIssuesApiUrl","ResponseError","fetch"],"mappings":"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"}
1
+ {"version":3,"file":"api.cjs.js","sources":["../src/api.ts"],"sourcesContent":["import fetch, { RequestInit } from 'node-fetch';\nimport {\n Filter,\n Issue,\n Project,\n JiraQueryResults,\n} from '@axis-backstage/plugin-jira-dashboard-common';\n\nimport type { ConfigInstance } from './config';\nimport { jqlQueryBuilder } from './queries';\nimport type { JiraProject } from './lib';\nimport { getApiUrl, replaceProjectApiUrl, replaceIssuesApiUrl } from './lib';\nimport { ResponseError } from '@backstage/errors';\n\nexport const getProjectInfo = async (\n project: JiraProject,\n): Promise<Project> => {\n const { projectKey, instance } = project;\n const response = await callApi(\n instance,\n `${getApiUrl(instance)}project/${projectKey}`,\n {\n method: 'GET',\n headers: {\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\n const projectResponse = await response.json();\n replaceProjectApiUrl(project.instance, projectResponse);\n return projectResponse;\n};\n\nexport const getFilterById = async (\n id: string,\n instance: ConfigInstance,\n): Promise<Filter> => {\n const response = await callApi(\n instance,\n `${getApiUrl(instance)}filter/${id}`,\n {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\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 getQueryUrl = (instance: ConfigInstance, jql?: string): string => {\n const baseUrl = getApiUrl(instance);\n\n if (instance.useApiV3) {\n const baseUrlWithEndpoint = `${baseUrl}search/jql`;\n if (jql) {\n return `${baseUrlWithEndpoint}?jql=${jql}&fields=*all`;\n }\n return baseUrlWithEndpoint;\n }\n\n const baseUrlWithEndpoint = `${baseUrl}search`;\n if (jql) {\n return `${baseUrlWithEndpoint}?jql=${jql}`;\n }\n return baseUrlWithEndpoint;\n};\n\nexport const getIssuesByFilter = async (\n projects: JiraProject[],\n components: string[],\n query: string,\n): Promise<Issue[]> => {\n const issues: Issue[] = [];\n for (const project of projects) {\n const { projectKey, instance } = project;\n const jql = jqlQueryBuilder({ project: [projectKey], components, query });\n const queryUrl = getQueryUrl(instance, jql);\n const response = await callApi(instance, queryUrl, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n })\n .then(resp => resp.json())\n .catch(() => null);\n if (response?.errorMessages) {\n throw Error(\n `JQL returned Error: JQL - ${jql} with error: ${response?.errorMessages?.[0]}`,\n );\n }\n if (response?.issues) {\n replaceIssuesApiUrl(project.instance, response.issues);\n issues.push(...response.issues);\n }\n }\n\n return 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 * Asynchronously searches for Jira issues using JQL.\n *\n * For more information about the available options, see the API 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<JiraQueryResults> => {\n const queryUrl = getQueryUrl(instance);\n const requestBody = { jql: jqlQuery, ...options };\n\n const response = await callApi(instance, queryUrl, {\n method: 'POST',\n body: JSON.stringify(requestBody),\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n const jsonResponse = (await response.json()) as JiraQueryResults;\n replaceIssuesApiUrl(instance, jsonResponse.issues);\n return jsonResponse;\n};\n\nexport const getIssuesByComponent = async (\n projects: JiraProject[],\n componentKeys: string,\n query?: string,\n): Promise<Issue[]> => {\n // Return an empty array if no projects are provided\n if (projects.length === 0) {\n return [];\n }\n\n const projectKeys = projects.map(project => project.projectKey);\n const components = componentKeys\n .split(',')\n .map(component => component.trim());\n\n const jql = jqlQueryBuilder({\n project: projectKeys,\n components,\n query,\n });\n\n const { instance } = projects[0];\n\n try {\n const queryUrl = getQueryUrl(instance, jql);\n const response = await callApi(instance, queryUrl, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n }).then(resp => resp.json());\n\n if (!response.issues || response.issues.length === 0) {\n return [];\n }\n\n replaceIssuesApiUrl(instance, response.issues);\n return response.issues;\n } catch (error: any) {\n if (error.message.includes(\"does not exist for the field 'project'\")) {\n return [];\n }\n throw error;\n }\n};\nexport async function getProjectAvatar(url: string, instance: ConfigInstance) {\n return callApi(instance, url);\n}\n\n/**\n * Call the Jira API using fetch.\n *\n * This function injects the auth token and custom headers.\n *\n * @public\n */\nexport async function callApi(\n instance: ConfigInstance,\n url: string,\n init?: RequestInit,\n) {\n const requestInit = init ?? { method: 'GET' };\n\n // Inject custom headers from config, Authorization token and headers from the\n // request\n requestInit.headers = {\n ...instance.headers,\n Authorization: instance.token,\n ...requestInit.headers,\n };\n return fetch(url, requestInit);\n}\n"],"names":["getApiUrl","replaceProjectApiUrl","baseUrlWithEndpoint","jqlQueryBuilder","replaceIssuesApiUrl","ResponseError","fetch"],"mappings":";;;;;;;;;;;AAca,MAAA,cAAA,GAAiB,OAC5B,OACqB,KAAA;AACrB,EAAM,MAAA,EAAE,UAAY,EAAA,QAAA,EAAa,GAAA,OAAA;AACjC,EAAA,MAAM,WAAW,MAAM,OAAA;AAAA,IACrB,QAAA;AAAA,IACA,CAAG,EAAAA,aAAA,CAAU,QAAQ,CAAC,WAAW,UAAU,CAAA,CAAA;AAAA,IAC3C;AAAA,MACE,MAAQ,EAAA,KAAA;AAAA,MACR,OAAS,EAAA;AAAA,QACP,MAAQ,EAAA;AAAA;AACV;AACF,GACF;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;AAAA,KAC5E;AAAA;AAGF,EAAM,MAAA,eAAA,GAAkB,MAAM,QAAA,CAAS,IAAK,EAAA;AAC5C,EAAqBC,wBAAA,CAAA,OAAA,CAAQ,UAAU,eAAe,CAAA;AACtD,EAAO,OAAA,eAAA;AACT;AAEa,MAAA,aAAA,GAAgB,OAC3B,EAAA,EACA,QACoB,KAAA;AACpB,EAAA,MAAM,WAAW,MAAM,OAAA;AAAA,IACrB,QAAA;AAAA,IACA,CAAG,EAAAD,aAAA,CAAU,QAAQ,CAAC,UAAU,EAAE,CAAA,CAAA;AAAA,IAClC;AAAA,MACE,MAAQ,EAAA,KAAA;AAAA,MACR,OAAS,EAAA;AAAA,QACP,MAAQ,EAAA;AAAA;AACV;AACF,GACF;AACA,EAAI,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AAC3B,IAAA,MAAM,KAAM,CAAA,CAAA,EAAG,QAAS,CAAA,MAAM,CAAE,CAAA,CAAA;AAAA;AAElC,EAAM,MAAA,YAAA,GAAe,MAAM,QAAA,CAAS,IAAK,EAAA;AACzC,EAAA,OAAO,EAAE,IAAM,EAAA,YAAA,CAAa,IAAM,EAAA,KAAA,EAAO,aAAa,GAAI,EAAA;AAC5D;AAEa,MAAA,WAAA,GAAc,CAAC,QAAA,EAA0B,GAAyB,KAAA;AAC7E,EAAM,MAAA,OAAA,GAAUA,cAAU,QAAQ,CAAA;AAElC,EAAA,IAAI,SAAS,QAAU,EAAA;AACrB,IAAME,MAAAA,oBAAAA,GAAsB,GAAG,OAAO,CAAA,UAAA,CAAA;AACtC,IAAA,IAAI,GAAK,EAAA;AACP,MAAO,OAAA,CAAA,EAAGA,oBAAmB,CAAA,KAAA,EAAQ,GAAG,CAAA,YAAA,CAAA;AAAA;AAE1C,IAAOA,OAAAA,oBAAAA;AAAA;AAGT,EAAM,MAAA,mBAAA,GAAsB,GAAG,OAAO,CAAA,MAAA,CAAA;AACtC,EAAA,IAAI,GAAK,EAAA;AACP,IAAO,OAAA,CAAA,EAAG,mBAAmB,CAAA,KAAA,EAAQ,GAAG,CAAA,CAAA;AAAA;AAE1C,EAAO,OAAA,mBAAA;AACT;AAEO,MAAM,iBAAoB,GAAA,OAC/B,QACA,EAAA,UAAA,EACA,KACqB,KAAA;AACrB,EAAA,MAAM,SAAkB,EAAC;AACzB,EAAA,KAAA,MAAW,WAAW,QAAU,EAAA;AAC9B,IAAM,MAAA,EAAE,UAAY,EAAA,QAAA,EAAa,GAAA,OAAA;AACjC,IAAM,MAAA,GAAA,GAAMC,wBAAgB,EAAE,OAAA,EAAS,CAAC,UAAU,CAAA,EAAG,UAAY,EAAA,KAAA,EAAO,CAAA;AACxE,IAAM,MAAA,QAAA,GAAW,WAAY,CAAA,QAAA,EAAU,GAAG,CAAA;AAC1C,IAAA,MAAM,QAAW,GAAA,MAAM,OAAQ,CAAA,QAAA,EAAU,QAAU,EAAA;AAAA,MACjD,MAAQ,EAAA,KAAA;AAAA,MACR,OAAS,EAAA;AAAA,QACP,MAAQ,EAAA;AAAA;AACV,KACD,CACE,CAAA,IAAA,CAAK,CAAQ,IAAA,KAAA,IAAA,CAAK,MAAM,CAAA,CACxB,KAAM,CAAA,MAAM,IAAI,CAAA;AACnB,IAAA,IAAI,UAAU,aAAe,EAAA;AAC3B,MAAM,MAAA,KAAA;AAAA,QACJ,8BAA8B,GAAG,CAAA,aAAA,EAAgB,QAAU,EAAA,aAAA,GAAgB,CAAC,CAAC,CAAA;AAAA,OAC/E;AAAA;AAEF,IAAA,IAAI,UAAU,MAAQ,EAAA;AACpB,MAAoBC,uBAAA,CAAA,OAAA,CAAQ,QAAU,EAAA,QAAA,CAAS,MAAM,CAAA;AACrD,MAAO,MAAA,CAAA,IAAA,CAAK,GAAG,QAAA,CAAS,MAAM,CAAA;AAAA;AAChC;AAGF,EAAO,OAAA,MAAA;AACT;AA4BO,MAAM,UAAa,GAAA,OACxB,QACA,EAAA,QAAA,EACA,OAC8B,KAAA;AAC9B,EAAM,MAAA,QAAA,GAAW,YAAY,QAAQ,CAAA;AACrC,EAAA,MAAM,WAAc,GAAA,EAAE,GAAK,EAAA,QAAA,EAAU,GAAG,OAAQ,EAAA;AAEhD,EAAA,MAAM,QAAW,GAAA,MAAM,OAAQ,CAAA,QAAA,EAAU,QAAU,EAAA;AAAA,IACjD,MAAQ,EAAA,MAAA;AAAA,IACR,IAAA,EAAM,IAAK,CAAA,SAAA,CAAU,WAAW,CAAA;AAAA,IAChC,OAAS,EAAA;AAAA,MACP,MAAQ,EAAA,kBAAA;AAAA,MACR,cAAgB,EAAA;AAAA;AAClB,GACD,CAAA;AACD,EAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,IAAM,MAAA,MAAMC,oBAAc,CAAA,YAAA,CAAa,QAAQ,CAAA;AAAA;AAEjD,EAAM,MAAA,YAAA,GAAgB,MAAM,QAAA,CAAS,IAAK,EAAA;AAC1C,EAAoBD,uBAAA,CAAA,QAAA,EAAU,aAAa,MAAM,CAAA;AACjD,EAAO,OAAA,YAAA;AACT;AAEO,MAAM,oBAAuB,GAAA,OAClC,QACA,EAAA,aAAA,EACA,KACqB,KAAA;AAErB,EAAI,IAAA,QAAA,CAAS,WAAW,CAAG,EAAA;AACzB,IAAA,OAAO,EAAC;AAAA;AAGV,EAAA,MAAM,WAAc,GAAA,QAAA,CAAS,GAAI,CAAA,CAAA,OAAA,KAAW,QAAQ,UAAU,CAAA;AAC9D,EAAM,MAAA,UAAA,GAAa,cAChB,KAAM,CAAA,GAAG,EACT,GAAI,CAAA,CAAA,SAAA,KAAa,SAAU,CAAA,IAAA,EAAM,CAAA;AAEpC,EAAA,MAAM,MAAMD,uBAAgB,CAAA;AAAA,IAC1B,OAAS,EAAA,WAAA;AAAA,IACT,UAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,EAAE,QAAA,EAAa,GAAA,QAAA,CAAS,CAAC,CAAA;AAE/B,EAAI,IAAA;AACF,IAAM,MAAA,QAAA,GAAW,WAAY,CAAA,QAAA,EAAU,GAAG,CAAA;AAC1C,IAAA,MAAM,QAAW,GAAA,MAAM,OAAQ,CAAA,QAAA,EAAU,QAAU,EAAA;AAAA,MACjD,MAAQ,EAAA,KAAA;AAAA,MACR,OAAS,EAAA;AAAA,QACP,MAAQ,EAAA;AAAA;AACV,KACD,CAAE,CAAA,IAAA,CAAK,CAAQ,IAAA,KAAA,IAAA,CAAK,MAAM,CAAA;AAE3B,IAAA,IAAI,CAAC,QAAS,CAAA,MAAA,IAAU,QAAS,CAAA,MAAA,CAAO,WAAW,CAAG,EAAA;AACpD,MAAA,OAAO,EAAC;AAAA;AAGV,IAAoBC,uBAAA,CAAA,QAAA,EAAU,SAAS,MAAM,CAAA;AAC7C,IAAA,OAAO,QAAS,CAAA,MAAA;AAAA,WACT,KAAY,EAAA;AACnB,IAAA,IAAI,KAAM,CAAA,OAAA,CAAQ,QAAS,CAAA,wCAAwC,CAAG,EAAA;AACpE,MAAA,OAAO,EAAC;AAAA;AAEV,IAAM,MAAA,KAAA;AAAA;AAEV;AACsB,eAAA,gBAAA,CAAiB,KAAa,QAA0B,EAAA;AAC5E,EAAO,OAAA,OAAA,CAAQ,UAAU,GAAG,CAAA;AAC9B;AASsB,eAAA,OAAA,CACpB,QACA,EAAA,GAAA,EACA,IACA,EAAA;AACA,EAAA,MAAM,WAAc,GAAA,IAAA,IAAQ,EAAE,MAAA,EAAQ,KAAM,EAAA;AAI5C,EAAA,WAAA,CAAY,OAAU,GAAA;AAAA,IACpB,GAAG,QAAS,CAAA,OAAA;AAAA,IACZ,eAAe,QAAS,CAAA,KAAA;AAAA,IACxB,GAAG,WAAY,CAAA;AAAA,GACjB;AACA,EAAO,OAAAE,sBAAA,CAAM,KAAK,WAAW,CAAA;AAC/B;;;;;;;;;;;"}
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var errors = require('@backstage/errors');
4
+ var config = require('@backstage/config');
5
+ var types = require('@backstage/types');
4
6
 
5
7
  function parseHeaders(config) {
6
8
  if (!config) {
@@ -20,6 +22,16 @@ const JIRA_CONFIG_HEADERS = "headers";
20
22
  const JIRA_CONFIG_USER_EMAIL_SUFFIX = "userEmailSuffix";
21
23
  const JIRA_CONFIG_ANNOTATION = "annotationPrefix";
22
24
  const JIRA_FILTERS = "defaultFilters";
25
+ const JIRA_CONFIG_USE_API_V3 = "useApiV3";
26
+ const JIRA_CONFIG_CACHE_TTL = "cacheTtl";
27
+ function getCacheTtl(instanceConfig) {
28
+ if (!instanceConfig.has(JIRA_CONFIG_CACHE_TTL)) {
29
+ return 36e5;
30
+ }
31
+ return types.durationToMilliseconds(
32
+ config.readDurationFromConfig(instanceConfig, { key: JIRA_CONFIG_CACHE_TTL })
33
+ );
34
+ }
23
35
  class JiraConfig {
24
36
  instances = {};
25
37
  /**
@@ -50,7 +62,9 @@ class JiraConfig {
50
62
  name: filterConfig.getString("name"),
51
63
  shortName: filterConfig.getString("shortName"),
52
64
  query: filterConfig.getString("query")
53
- }))
65
+ })),
66
+ useApiV3: inst.getOptionalBoolean(JIRA_CONFIG_USE_API_V3) ?? false,
67
+ cacheTtl: getCacheTtl(inst)
54
68
  };
55
69
  });
56
70
  } else {
@@ -64,7 +78,9 @@ class JiraConfig {
64
78
  name: filterConfig.getString("name"),
65
79
  shortName: filterConfig.getString("shortName"),
66
80
  query: filterConfig.getString("query")
67
- }))
81
+ })),
82
+ useApiV3: jira.getOptionalBoolean(JIRA_CONFIG_USE_API_V3) ?? false,
83
+ cacheTtl: getCacheTtl(jira)
68
84
  };
69
85
  }
70
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.cjs.js","sources":["../src/config.ts"],"sourcesContent":["import { ConflictError, ServiceUnavailableError } from '@backstage/errors';\nimport { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Filter } from '@axis-backstage/plugin-jira-dashboard-common';\n\ntype Config = ReturnType<RootConfigService['getConfig']>;\n\nfunction parseHeaders(config: Config | undefined): Record<string, string> {\n if (!config) {\n return {};\n }\n return Object.fromEntries(\n config.keys().map(key => {\n const value = config.getString(key);\n return [key, value] as const;\n }),\n );\n}\n\n/**\n * The configuration of a Jira instance\n *\n * @public\n */\nexport type ConfigInstance = {\n token: string;\n headers: Record<string, string>;\n baseUrl: string;\n apiUrl?: string;\n userEmailSuffix?: string;\n defaultFilters?: Filter[];\n};\n\nconst JIRA_CONFIG_BASE_URL = 'baseUrl';\nconst JIRA_CONFIG_API_URL = 'apiUrl';\nconst JIRA_CONFIG_TOKEN = 'token';\nconst JIRA_CONFIG_HEADERS = 'headers';\nconst JIRA_CONFIG_USER_EMAIL_SUFFIX = 'userEmailSuffix';\nconst JIRA_CONFIG_ANNOTATION = 'annotationPrefix';\nconst JIRA_FILTERS = 'defaultFilters';\n\n/**\n * Class for reading Jira configuration from the root config\n *\n * @public\n */\nexport class JiraConfig {\n private instances: Record<string, ConfigInstance> = {};\n /**\n * The annotation prefix to use for Jira annotations\n */\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 headers: parseHeaders(inst.getOptionalConfig(JIRA_CONFIG_HEADERS)),\n baseUrl: inst.getString(JIRA_CONFIG_BASE_URL),\n apiUrl: inst.getOptionalString(JIRA_CONFIG_API_URL),\n userEmailSuffix: inst.getOptionalString(\n JIRA_CONFIG_USER_EMAIL_SUFFIX,\n ),\n defaultFilters: inst\n .getOptionalConfigArray(JIRA_FILTERS)\n ?.map(filterConfig => ({\n name: filterConfig.getString('name'),\n shortName: filterConfig.getString('shortName'),\n query: filterConfig.getString('query'),\n })),\n };\n });\n } else {\n // Default form\n this.instances.default = {\n token: jira.getString(JIRA_CONFIG_TOKEN),\n headers: parseHeaders(jira.getOptionalConfig(JIRA_CONFIG_HEADERS)),\n baseUrl: jira.getString(JIRA_CONFIG_BASE_URL),\n apiUrl: jira.getOptionalString(JIRA_CONFIG_API_URL),\n userEmailSuffix: jira.getOptionalString(JIRA_CONFIG_USER_EMAIL_SUFFIX),\n defaultFilters: jira\n .getOptionalConfigArray(JIRA_FILTERS)\n ?.map(filterConfig => ({\n name: filterConfig.getString('name'),\n shortName: filterConfig.getString('shortName'),\n query: filterConfig.getString('query'),\n })),\n };\n }\n }\n\n /**\n * Create a JiraConfig from the root config\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 /**\n * Returns the configuration all instances\n */\n getInstances() {\n return Object.getOwnPropertyNames(this.instances);\n }\n\n /**\n * Get the jira config for a specific instance\n */\n getInstance(instanceName?: string): ConfigInstance {\n return this.forInstance(instanceName ?? 'default');\n }\n\n /**\n * Get the jira base url for a given instance\n */\n resolveJiraBaseUrl(instanceName: string): string {\n const instance = this.forInstance(instanceName);\n return instance.baseUrl;\n }\n\n /**\n * Get the auth token for a given instance\n */\n resolveJiraToken(instanceName: string): string {\n const instance = this.forInstance(instanceName);\n return instance.token;\n }\n\n /**\n * Get the email suffice for a given instance\n */\n resolveUserEmailSuffix(instanceName: string): string | undefined {\n const instance = this.forInstance(instanceName);\n return instance.userEmailSuffix;\n }\n\n /**\n * Get the defined default filters for a given instance\n */\n resolveDefaultFilters(instanceName: string): Filter[] | undefined {\n const instance = this.forInstance(instanceName);\n return instance.defaultFilters;\n }\n}\n"],"names":["ConflictError","ServiceUnavailableError"],"mappings":"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"}
1
+ {"version":3,"file":"config.cjs.js","sources":["../src/config.ts"],"sourcesContent":["import { ConflictError, ServiceUnavailableError } from '@backstage/errors';\nimport { RootConfigService } from '@backstage/backend-plugin-api';\nimport { readDurationFromConfig } from '@backstage/config';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { Filter } from '@axis-backstage/plugin-jira-dashboard-common';\n\ntype Config = ReturnType<RootConfigService['getConfig']>;\n\nfunction parseHeaders(config: Config | undefined): Record<string, string> {\n if (!config) {\n return {};\n }\n return Object.fromEntries(\n config.keys().map(key => {\n const value = config.getString(key);\n return [key, value] as const;\n }),\n );\n}\n\n/**\n * The configuration of a Jira instance\n *\n * @public\n */\nexport type ConfigInstance = {\n token: string;\n headers: Record<string, string>;\n baseUrl: string;\n apiUrl?: string;\n userEmailSuffix?: string;\n defaultFilters?: Filter[];\n useApiV3?: boolean;\n cacheTtl: number; // TTL in milliseconds\n};\n\nconst JIRA_CONFIG_BASE_URL = 'baseUrl';\nconst JIRA_CONFIG_API_URL = 'apiUrl';\nconst JIRA_CONFIG_TOKEN = 'token';\nconst JIRA_CONFIG_HEADERS = 'headers';\nconst JIRA_CONFIG_USER_EMAIL_SUFFIX = 'userEmailSuffix';\nconst JIRA_CONFIG_ANNOTATION = 'annotationPrefix';\nconst JIRA_FILTERS = 'defaultFilters';\nconst JIRA_CONFIG_USE_API_V3 = 'useApiV3';\nconst JIRA_CONFIG_CACHE_TTL = 'cacheTtl';\n\n/**\n * Get cache TTL in milliseconds for a Jira instance configuration\n * Defaults to 1 hour (3,600,000 ms) if not specified\n */\nfunction getCacheTtl(instanceConfig: Config): number {\n if (!instanceConfig.has(JIRA_CONFIG_CACHE_TTL)) {\n return 3_600_000;\n }\n\n return durationToMilliseconds(\n readDurationFromConfig(instanceConfig, { key: JIRA_CONFIG_CACHE_TTL }),\n );\n}\n\n/**\n * Class for reading Jira configuration from the root config\n *\n * @public\n */\nexport class JiraConfig {\n private instances: Record<string, ConfigInstance> = {};\n /**\n * The annotation prefix to use for Jira annotations\n */\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 headers: parseHeaders(inst.getOptionalConfig(JIRA_CONFIG_HEADERS)),\n baseUrl: inst.getString(JIRA_CONFIG_BASE_URL),\n apiUrl: inst.getOptionalString(JIRA_CONFIG_API_URL),\n userEmailSuffix: inst.getOptionalString(\n JIRA_CONFIG_USER_EMAIL_SUFFIX,\n ),\n defaultFilters: inst\n .getOptionalConfigArray(JIRA_FILTERS)\n ?.map(filterConfig => ({\n name: filterConfig.getString('name'),\n shortName: filterConfig.getString('shortName'),\n query: filterConfig.getString('query'),\n })),\n useApiV3: inst.getOptionalBoolean(JIRA_CONFIG_USE_API_V3) ?? false,\n cacheTtl: getCacheTtl(inst),\n };\n });\n } else {\n // Default form\n this.instances.default = {\n token: jira.getString(JIRA_CONFIG_TOKEN),\n headers: parseHeaders(jira.getOptionalConfig(JIRA_CONFIG_HEADERS)),\n baseUrl: jira.getString(JIRA_CONFIG_BASE_URL),\n apiUrl: jira.getOptionalString(JIRA_CONFIG_API_URL),\n userEmailSuffix: jira.getOptionalString(JIRA_CONFIG_USER_EMAIL_SUFFIX),\n defaultFilters: jira\n .getOptionalConfigArray(JIRA_FILTERS)\n ?.map(filterConfig => ({\n name: filterConfig.getString('name'),\n shortName: filterConfig.getString('shortName'),\n query: filterConfig.getString('query'),\n })),\n useApiV3: jira.getOptionalBoolean(JIRA_CONFIG_USE_API_V3) ?? false,\n cacheTtl: getCacheTtl(jira),\n };\n }\n }\n\n /**\n * Create a JiraConfig from the root config\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 /**\n * Returns the configuration all instances\n */\n getInstances() {\n return Object.getOwnPropertyNames(this.instances);\n }\n\n /**\n * Get the jira config for a specific instance\n */\n getInstance(instanceName?: string): ConfigInstance {\n return this.forInstance(instanceName ?? 'default');\n }\n\n /**\n * Get the jira base url for a given instance\n */\n resolveJiraBaseUrl(instanceName: string): string {\n const instance = this.forInstance(instanceName);\n return instance.baseUrl;\n }\n\n /**\n * Get the auth token for a given instance\n */\n resolveJiraToken(instanceName: string): string {\n const instance = this.forInstance(instanceName);\n return instance.token;\n }\n\n /**\n * Get the email suffice for a given instance\n */\n resolveUserEmailSuffix(instanceName: string): string | undefined {\n const instance = this.forInstance(instanceName);\n return instance.userEmailSuffix;\n }\n\n /**\n * Get the defined default filters for a given instance\n */\n resolveDefaultFilters(instanceName: string): Filter[] | undefined {\n const instance = this.forInstance(instanceName);\n return instance.defaultFilters;\n }\n}\n"],"names":["durationToMilliseconds","readDurationFromConfig","ConflictError","ServiceUnavailableError"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -23,6 +23,8 @@ type ConfigInstance = {
23
23
  apiUrl?: string;
24
24
  userEmailSuffix?: string;
25
25
  defaultFilters?: Filter[];
26
+ useApiV3?: boolean;
27
+ cacheTtl: number;
26
28
  };
27
29
  /**
28
30
  * Class for reading Jira configuration from the root config
@@ -11,7 +11,9 @@ const getProjectResponse = async (project, cache) => {
11
11
  }
12
12
  try {
13
13
  projectResponse = await api.getProjectInfo(project);
14
- cache.set(project.fullProjectKey, projectResponse);
14
+ cache.set(project.fullProjectKey, projectResponse, {
15
+ ttl: project.instance.cacheTtl
16
+ });
15
17
  } catch (err) {
16
18
  if (err.message !== 200) {
17
19
  throw Error(
@@ -30,7 +32,7 @@ const getJqlResponse = async (jql, config, cache, searchOptions) => {
30
32
  }
31
33
  try {
32
34
  issuesResponse = (await api.searchJira(config, jql, searchOptions)).issues;
33
- cache.set(cacheKey, issuesResponse);
35
+ cache.set(cacheKey, issuesResponse, { ttl: config.cacheTtl });
34
36
  } catch (err) {
35
37
  if (err.message !== 200) {
36
38
  throw Error(
@@ -87,7 +89,7 @@ async function getJiraProjectsFromKeys(projectKeys, instance, cache) {
87
89
  projectInfo = await api.getProjectInfo({
88
90
  projectKey: key,
89
91
  instance});
90
- cache.set(key, projectInfo);
92
+ cache.set(key, projectInfo, { ttl: instance.cacheTtl });
91
93
  }
92
94
  jiraProjects.push({
93
95
  instance,
@@ -1 +1 @@
1
- {"version":3,"file":"service.cjs.js","sources":["../../src/service/service.ts"],"sourcesContent":["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 const cacheKey = `${config.baseUrl} ${jql}`;\n\n issuesResponse = (await cache.get(cacheKey)) as Issue[];\n\n if (issuesResponse) {\n return issuesResponse;\n }\n\n try {\n issuesResponse = (await searchJira(config, jql, searchOptions)).issues;\n cache.set(cacheKey, 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 filterName: string,\n): Promise<Issue[]> => {\n let jql = `assignee = \"${username}\" AND resolution = Unresolved ORDER BY priority DESC, updated DESC`;\n if (filterName !== 'default') {\n for (const filter of config.defaultFilters || []) {\n if (filterName === filter.name) {\n jql = `assignee = \"${username}\" AND ${filter.query}`;\n }\n }\n }\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};\nasync function getJiraProjectsFromKeys(\n projectKeys: string[],\n instance: ConfigInstance,\n cache: CacheService,\n): Promise<JiraProject[]> {\n const jiraProjects: JiraProject[] = [];\n for (const key of projectKeys) {\n const cachedProject = (await cache.get(key)) as Project;\n let projectInfo: Project;\n\n if (cachedProject) {\n projectInfo = cachedProject;\n } else {\n projectInfo = await getProjectInfo({\n projectKey: key,\n instance,\n fullProjectKey: '',\n });\n cache.set(key, projectInfo);\n }\n\n jiraProjects.push({\n instance,\n fullProjectKey: projectInfo.key,\n projectKey: projectInfo.key,\n });\n }\n return jiraProjects;\n}\nexport const getIssuesFromFilters = async (\n projectKeys: string[],\n components: string[],\n filters: Filter[],\n instance: ConfigInstance,\n cache: CacheService,\n jqlAnnotation?: string,\n): Promise<JiraDataResponse[]> => {\n const projects = await getJiraProjectsFromKeys(projectKeys, instance, cache);\n return await Promise.all(\n filters.map(async filter => {\n const combinedQuery = `${jqlAnnotation ? `(${jqlAnnotation}) AND ` : ''}${\n filter.query\n }`;\n return {\n name: filter.name,\n query: jqlQueryBuilder({\n project: projectKeys,\n components,\n query: combinedQuery,\n }),\n type: 'filter',\n issues: await getIssuesByFilter(projects, components, combinedQuery),\n };\n }),\n );\n};\n\nexport const getIssuesFromComponents = async (\n projectKeys: string[],\n componentAnnotations: string[],\n instance: ConfigInstance,\n cache: CacheService,\n jqlAnnotation?: string,\n): Promise<JiraDataResponse[]> => {\n const projects = await getJiraProjectsFromKeys(projectKeys, instance, cache);\n return await Promise.all(\n componentAnnotations.map(async componentKey => ({\n name: componentKey,\n query: jqlQueryBuilder({\n project: projectKeys,\n components: [componentKey],\n query: jqlQueryBuilder({\n project: projectKeys,\n components: componentAnnotations,\n query: jqlAnnotation,\n }),\n }),\n type: 'component',\n issues: await getIssuesByComponent(projects, componentKey, jqlAnnotation),\n })),\n );\n};\n"],"names":["getProjectInfo","searchJira","getFilterById","jqlQueryBuilder","getIssuesByFilter","getIssuesByComponent"],"mappings":"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"}
1
+ {"version":3,"file":"service.cjs.js","sources":["../../src/service/service.ts"],"sourcesContent":["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 ttl: project.instance.cacheTtl,\n });\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 const cacheKey = `${config.baseUrl} ${jql}`;\n\n issuesResponse = (await cache.get(cacheKey)) as Issue[];\n\n if (issuesResponse) {\n return issuesResponse;\n }\n\n try {\n issuesResponse = (await searchJira(config, jql, searchOptions)).issues;\n cache.set(cacheKey, issuesResponse, { ttl: config.cacheTtl });\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 filterName: string,\n): Promise<Issue[]> => {\n let jql = `assignee = \"${username}\" AND resolution = Unresolved ORDER BY priority DESC, updated DESC`;\n if (filterName !== 'default') {\n for (const filter of config.defaultFilters || []) {\n if (filterName === filter.name) {\n jql = `assignee = \"${username}\" AND ${filter.query}`;\n }\n }\n }\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};\nasync function getJiraProjectsFromKeys(\n projectKeys: string[],\n instance: ConfigInstance,\n cache: CacheService,\n): Promise<JiraProject[]> {\n const jiraProjects: JiraProject[] = [];\n for (const key of projectKeys) {\n const cachedProject = (await cache.get(key)) as Project;\n let projectInfo: Project;\n\n if (cachedProject) {\n projectInfo = cachedProject;\n } else {\n projectInfo = await getProjectInfo({\n projectKey: key,\n instance,\n fullProjectKey: '',\n });\n cache.set(key, projectInfo, { ttl: instance.cacheTtl });\n }\n\n jiraProjects.push({\n instance,\n fullProjectKey: projectInfo.key,\n projectKey: projectInfo.key,\n });\n }\n return jiraProjects;\n}\nexport const getIssuesFromFilters = async (\n projectKeys: string[],\n components: string[],\n filters: Filter[],\n instance: ConfigInstance,\n cache: CacheService,\n jqlAnnotation?: string,\n): Promise<JiraDataResponse[]> => {\n const projects = await getJiraProjectsFromKeys(projectKeys, instance, cache);\n return await Promise.all(\n filters.map(async filter => {\n const combinedQuery = `${jqlAnnotation ? `(${jqlAnnotation}) AND ` : ''}${\n filter.query\n }`;\n return {\n name: filter.name,\n query: jqlQueryBuilder({\n project: projectKeys,\n components,\n query: combinedQuery,\n }),\n type: 'filter',\n issues: await getIssuesByFilter(projects, components, combinedQuery),\n };\n }),\n );\n};\n\nexport const getIssuesFromComponents = async (\n projectKeys: string[],\n componentAnnotations: string[],\n instance: ConfigInstance,\n cache: CacheService,\n jqlAnnotation?: string,\n): Promise<JiraDataResponse[]> => {\n const projects = await getJiraProjectsFromKeys(projectKeys, instance, cache);\n return await Promise.all(\n componentAnnotations.map(async componentKey => ({\n name: componentKey,\n query: jqlQueryBuilder({\n project: projectKeys,\n components: [componentKey],\n query: jqlQueryBuilder({\n project: projectKeys,\n components: componentAnnotations,\n query: jqlAnnotation,\n }),\n }),\n type: 'component',\n issues: await getIssuesByComponent(projects, componentKey, jqlAnnotation),\n })),\n );\n};\n"],"names":["getProjectInfo","searchJira","getFilterById","jqlQueryBuilder","getIssuesByFilter","getIssuesByComponent"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axis-backstage/plugin-jira-dashboard-backend",
3
- "version": "4.9.1",
3
+ "version": "6.0.0",
4
4
  "main": "dist/index.cjs.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -37,7 +37,9 @@
37
37
  "@backstage/backend-plugin-api": "^1.4.2",
38
38
  "@backstage/catalog-client": "^1.11.0",
39
39
  "@backstage/catalog-model": "^1.7.5",
40
+ "@backstage/config": "^1.3.5",
40
41
  "@backstage/errors": "^1.2.7",
42
+ "@backstage/types": "^1.2.2",
41
43
  "express": "^4.17.1",
42
44
  "express-promise-router": "^4.1.0",
43
45
  "node-fetch": "^2.6.7"