Skip to content

Commit e595239

Browse files
author
Frida Englund
committed
Add jql annotation to jira plugin
1 parent 2a2afb8 commit e595239

9 files changed

Lines changed: 60 additions & 8 deletions

File tree

examples/entities.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ metadata:
1414
name: example-website
1515
annotations:
1616
jira.com/project-key: BS
17-
# jira/project-key: BS
17+
jira.com/jql: resolution = Unresolved order by updated DESC
1818

1919
spec:
2020
type: website

plugins/jira-dashboard-backend/src/api.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,17 @@ export const getIssuesByFilter = async (
6262
projects: JiraProject[],
6363
components: string[],
6464
query: string,
65+
jqlInput?: string,
6566
): Promise<Issue[]> => {
6667
const issues: Issue[] = [];
6768
for (const project of projects) {
6869
const { projectKey, instance } = project;
69-
const jql = jqlQueryBuilder({ project: [projectKey], components, query });
70+
const jql = jqlQueryBuilder({
71+
project: [projectKey],
72+
components,
73+
query,
74+
jqlInput,
75+
});
7076
const response = await callApi(
7177
instance,
7278
`${getApiUrl(instance)}search?jql=${jql}`,
@@ -122,11 +128,11 @@ export type SearchOptions = {
122128
export const searchJira = async (
123129
instance: ConfigInstance,
124130
jqlQuery: string,
125-
options: SearchOptions,
131+
options?: SearchOptions,
126132
): Promise<JiraQueryResults> => {
127133
const response = await callApi(instance, `${getApiUrl(instance)}search`, {
128134
method: 'POST',
129-
body: JSON.stringify({ jql: jqlQuery, ...options }),
135+
body: JSON.stringify({ jql: jqlQuery, ...(options ?? {}) }),
130136
headers: {
131137
Accept: 'application/json',
132138
'Content-Type': 'application/json',
@@ -143,6 +149,7 @@ export const searchJira = async (
143149
export const getIssuesByComponent = async (
144150
projects: JiraProject[],
145151
componentKeys: string,
152+
jqlInput?: string,
146153
): Promise<Issue[]> => {
147154
// Return an empty array if no projects are provided
148155
if (projects.length === 0) {
@@ -157,6 +164,7 @@ export const getIssuesByComponent = async (
157164
const jql = jqlQueryBuilder({
158165
project: projectKeys,
159166
components,
167+
jqlInput,
160168
});
161169

162170
const { instance } = projects[0];

plugins/jira-dashboard-backend/src/lib.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
INCOMING_ISSUES_STATUS,
66
Project,
77
Issue,
8+
JQL,
89
} from '@axis-backstage/plugin-jira-dashboard-common';
910

1011
import type { ConfigInstance, JiraConfig } from './config';
@@ -15,6 +16,7 @@ export const getAnnotations = (config: JiraConfig) => {
1516
const projectKeyAnnotation = `${prefix}/${PROJECT_KEY_NAME}`;
1617
const componentsAnnotation = `${prefix}/${COMPONENTS_NAME}`;
1718
const filtersAnnotation = `${prefix}/${FILTERS_NAME}`;
19+
const jqlAnnotation = `${prefix}/${JQL}`;
1820
const incomingIssuesAnnotation = `${prefix}/${INCOMING_ISSUES_STATUS}`;
1921

2022
/* Adding support for Roadie's component annotation */
@@ -24,6 +26,7 @@ export const getAnnotations = (config: JiraConfig) => {
2426
projectKeyAnnotation,
2527
componentsAnnotation,
2628
filtersAnnotation,
29+
jqlAnnotation,
2730
incomingIssuesAnnotation,
2831
componentRoadieAnnotation,
2932
};

plugins/jira-dashboard-backend/src/queries.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type JqlQueryBuilderArgs = {
77
project: string | string[];
88
components?: string[];
99
query?: string;
10+
jqlInput?: string;
1011
};
1112

1213
/**
@@ -43,9 +44,15 @@ export const jqlQueryBuilder = ({
4344
project,
4445
components,
4546
query,
47+
jqlInput,
4648
}: JqlQueryBuilderArgs) => {
4749
const projectList = Array.isArray(project) ? project : [project];
4850
let jql = createEscapedIncludeQuery('project', projectList);
51+
52+
if (jqlInput) {
53+
jql += ` AND (${jqlInput})`;
54+
}
55+
4956
if (components?.length) {
5057
jql += ` AND ${createEscapedIncludeQuery('component', components)}`;
5158
}

plugins/jira-dashboard-backend/src/service/router.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export async function createRouter(
108108
projectKeyAnnotation,
109109
componentsAnnotation,
110110
filtersAnnotation,
111+
jqlAnnotation,
111112
incomingIssuesAnnotation,
112113
componentRoadieAnnotation,
113114
} = getAnnotations(config);
@@ -150,6 +151,8 @@ export async function createRouter(
150151
}
151152
}
152153

154+
const jql = entity.metadata.annotations?.[jqlAnnotation];
155+
153156
let userEntity: UserEntity | undefined;
154157

155158
try {
@@ -198,6 +201,7 @@ export async function createRouter(
198201
filters,
199202
instance,
200203
cache,
204+
jql,
201205
);
202206

203207
/* Adding support for Roadie's component annotation */
@@ -212,6 +216,7 @@ export async function createRouter(
212216
components,
213217
instance,
214218
cache,
219+
jql,
215220
);
216221
issues = issues.concat(componentIssues);
217222
}

plugins/jira-dashboard-backend/src/service/service.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const getJqlResponse = async (
4646
jql: string,
4747
config: ConfigInstance,
4848
cache: CacheService,
49-
searchOptions: SearchOptions,
49+
searchOptions?: SearchOptions,
5050
): Promise<Issue[]> => {
5151
let issuesResponse: Issue[];
5252

@@ -154,6 +154,7 @@ export const getIssuesFromFilters = async (
154154
filters: Filter[],
155155
instance: ConfigInstance,
156156
cache: CacheService,
157+
jql?: string,
157158
): Promise<JiraDataResponse[]> => {
158159
const projects = await getJiraProjectsFromKeys(projectKeys, instance, cache);
159160
return await Promise.all(
@@ -165,7 +166,7 @@ export const getIssuesFromFilters = async (
165166
query: filter.query,
166167
}),
167168
type: 'filter',
168-
issues: await getIssuesByFilter(projects, components, filter.query),
169+
issues: await getIssuesByFilter(projects, components, filter.query, jql),
169170
})),
170171
);
171172
};
@@ -175,6 +176,7 @@ export const getIssuesFromComponents = async (
175176
componentAnnotations: string[],
176177
instance: ConfigInstance,
177178
cache: CacheService,
179+
jql?: string,
178180
): Promise<JiraDataResponse[]> => {
179181
const projects = await getJiraProjectsFromKeys(projectKeys, instance, cache);
180182
return await Promise.all(
@@ -185,7 +187,7 @@ export const getIssuesFromComponents = async (
185187
components: [componentKey],
186188
}),
187189
type: 'component',
188-
issues: await getIssuesByComponent(projects, componentKey),
190+
issues: await getIssuesByComponent(projects, componentKey, jql),
189191
})),
190192
);
191193
};

plugins/jira-dashboard-common/src/annotations.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ export const COMPONENTS_NAME = 'components';
2020
*/
2121
export const FILTERS_NAME = 'filter-ids';
2222

23+
/**
24+
* The annotation name used to provide a JQL query to filter issues in Jira
25+
* @public
26+
*/
27+
export const JQL = 'jql';
28+
2329
/**
2430
* The annotation name used to provide the status for incoming issues in Jira
2531
* @public

plugins/jira-dashboard-common/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export type Project = {
7676
*/
7777
export type JiraResponse = {
7878
project: Project | Project[];
79-
data: JiraDataResponse[];
79+
data: JiraDataResponse[] | Issue[];
8080
};
8181

8282
/**

plugins/jira-dashboard/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,27 @@ metadata:
184184
jira.com/incoming-issues-status: Incoming # The name of the status for incoming issues in Jira. Default: New
185185
```
186186
187+
#### Custom JQL Annotation
188+
189+
You can use the `jira.com/jql` annotation to specify a custom [Jira Query Language (JQL)](https://support.atlassian.com/jira-software-cloud/docs/advanced-search-reference-jql/) query for your entity.
190+
**When this annotation is present, it will override the values set by `jira.com/components`, and `jira.com/filter-ids`.**
191+
This allows you to fully customize which issues are shown in the Jira Dashboard for your entity.
192+
193+
**Example:**
194+
195+
```yaml
196+
apiVersion: backstage.io/v1alpha1
197+
kind: Component
198+
metadata:
199+
annotations:
200+
jira.com/jql: component = OurProject AND Affected = 1.1
201+
```
202+
203+
**Note:**
204+
205+
- Make sure your JQL is valid and returns the issues you expect.
206+
- If you use `jira.com/jql`, you do not need to set `jira.com/components`, or `jira.com/filter-ids` for that entity. However, you still need to specify the `jira.com/project-key` annotation.
207+
187208
### New Frontend System (Alpha)
188209

189210
The Jira Dashboard plugin also has support for the [new alpha frontend system](https://backstage.io/docs/frontend-system/). Here is how you can set it up:

0 commit comments

Comments
 (0)