From bba9cdde702738fb0a5371609dda75cabbc302cf Mon Sep 17 00:00:00 2001 From: enaysaa Date: Tue, 19 Nov 2024 14:48:36 +0000 Subject: [PATCH] Support for user defined additional filters --- .changeset/two-students-care.md | 8 +++ plugins/jira-dashboard-backend/README.md | 22 +++++++ plugins/jira-dashboard-backend/api-report.md | 2 + plugins/jira-dashboard-backend/config.d.ts | 18 ++++++ .../jira-dashboard-backend/src/config.test.ts | 44 ++++++++++++++ plugins/jira-dashboard-backend/src/config.ts | 21 +++++++ .../src/filters.test.ts | 57 ++++++++++++++++++- plugins/jira-dashboard-backend/src/filters.ts | 11 +++- 8 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 .changeset/two-students-care.md diff --git a/.changeset/two-students-care.md b/.changeset/two-students-care.md new file mode 100644 index 0000000..efe3bf5 --- /dev/null +++ b/.changeset/two-students-care.md @@ -0,0 +1,8 @@ +--- +'@axis-backstage/plugin-jira-dashboard-backend': minor +--- + +Support for user defined additional filters +Issue https://github.com/AxisCommunications/backstage-plugins/issues/210 + +Signed-off-by: enaysaa diff --git a/plugins/jira-dashboard-backend/README.md b/plugins/jira-dashboard-backend/README.md index 54dceba..0d85051 100644 --- a/plugins/jira-dashboard-backend/README.md +++ b/plugins/jira-dashboard-backend/README.md @@ -73,6 +73,28 @@ metadata: jira.com/project-key: separate-jira-instance/my-project-key ``` +### Custom Jira Filters + +You can define custom Jira filters directly in your `app-config.yaml` file. This allows you to create and display filters beyond the ones provided by the plugin. + +#### Configuration + +To add custom filters, use the `defaultFilters` property within a Jira instance configuration: + +```yaml +jiraDashboard: + instances: + - name: my-jira-instance + # ... other configuration ... + defaultFilters: + - name: 'Open Bugs' + shortName: 'Bugs' + query: 'type = bug AND resolution = Unresolved ORDER BY updated DESC, priority DESC' + - name: 'Epics' + shortName: 'Epics' + query: 'type = epic AND resolution = Unresolved ORDER BY updated DESC, priority DESC' +``` + #### Authentication examples and trouble shooting Either "Basic Auth" or "Personal Acccess Tokens" can be used. diff --git a/plugins/jira-dashboard-backend/api-report.md b/plugins/jira-dashboard-backend/api-report.md index 49bf211..7cbbc97 100644 --- a/plugins/jira-dashboard-backend/api-report.md +++ b/plugins/jira-dashboard-backend/api-report.md @@ -4,6 +4,7 @@ ```ts import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { Filter } from '@axis-backstage/plugin-jira-dashboard-common'; import { JiraQueryResults } from '@axis-backstage/plugin-jira-dashboard-common'; import { RootConfigService } from '@backstage/backend-plugin-api'; @@ -13,6 +14,7 @@ export type ConfigInstance = { headers: Record; baseUrl: string; userEmailSuffix?: string; + defaultFilters?: Filter[]; }; // @public diff --git a/plugins/jira-dashboard-backend/config.d.ts b/plugins/jira-dashboard-backend/config.d.ts index 28737f9..8fbf030 100644 --- a/plugins/jira-dashboard-backend/config.d.ts +++ b/plugins/jira-dashboard-backend/config.d.ts @@ -31,6 +31,15 @@ export interface Config { */ annotationPrefix?: string; + /** + * Optional default filters for Jira queries + */ + defaultFilters?: { + name: string; + query: string; + shortName: string; + }[]; + // Type helper instances?: never; } @@ -69,6 +78,15 @@ export interface Config { * 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. */ userEmailSuffix?: string; + + /** + * Optional default filters for Jira queries + */ + defaultFilters?: { + name: string; + query: string; + shortName: string; + }[]; }[]; }; } diff --git a/plugins/jira-dashboard-backend/src/config.test.ts b/plugins/jira-dashboard-backend/src/config.test.ts index a67c323..a1f2536 100644 --- a/plugins/jira-dashboard-backend/src/config.test.ts +++ b/plugins/jira-dashboard-backend/src/config.test.ts @@ -116,4 +116,48 @@ describe('config', () => { expect(instance2.headers).toEqual({ 'Other-Header': 'other value' }); expect(instance2.userEmailSuffix).toBe('@backstage2.com'); }); + + it('should handle defaultFilters config', () => { + const mockConfig = mockServices.rootConfig({ + data: { + jiraDashboard: { + instances: [ + { + name: 'default', + baseUrl: 'http://jira.com', + token: 'token', + defaultFilters: [ + { + name: 'My Open Bugs', + shortName: 'MyBugs', + query: 'type = Bug AND resolution = Unresolved', + }, + { + name: 'High Priority Issues', + shortName: 'HighPrio', + query: 'priority = "High"', + }, + ], + }, + ], + }, + }, + }); + + const jiraConfig = JiraConfig.fromConfig(mockConfig); + const instance = jiraConfig.getInstance(); + + expect(instance.defaultFilters).toEqual([ + { + name: 'My Open Bugs', + shortName: 'MyBugs', + query: 'type = Bug AND resolution = Unresolved', + }, + { + name: 'High Priority Issues', + shortName: 'HighPrio', + query: 'priority = "High"', + }, + ]); + }); }); diff --git a/plugins/jira-dashboard-backend/src/config.ts b/plugins/jira-dashboard-backend/src/config.ts index 9925b8a..8b58968 100644 --- a/plugins/jira-dashboard-backend/src/config.ts +++ b/plugins/jira-dashboard-backend/src/config.ts @@ -1,5 +1,6 @@ import { ConflictError, ServiceUnavailableError } from '@backstage/errors'; import { RootConfigService } from '@backstage/backend-plugin-api'; +import { Filter } from '@axis-backstage/plugin-jira-dashboard-common'; type Config = ReturnType; @@ -25,6 +26,7 @@ export type ConfigInstance = { headers: Record; baseUrl: string; userEmailSuffix?: string; + defaultFilters?: Filter[]; }; const JIRA_CONFIG_BASE_URL = 'baseUrl'; @@ -32,6 +34,7 @@ const JIRA_CONFIG_TOKEN = 'token'; const JIRA_CONFIG_HEADERS = 'headers'; const JIRA_CONFIG_USER_EMAIL_SUFFIX = 'userEmailSuffix'; const JIRA_CONFIG_ANNOTATION = 'annotationPrefix'; +const JIRA_FILTERS = 'defaultFilters'; /** * Class for reading Jira configuration from the root config @@ -69,6 +72,13 @@ export class JiraConfig { userEmailSuffix: inst.getOptionalString( JIRA_CONFIG_USER_EMAIL_SUFFIX, ), + defaultFilters: inst + .getOptionalConfigArray(JIRA_FILTERS) + ?.map(filterConfig => ({ + name: filterConfig.getString('name'), + shortName: filterConfig.getString('shortName'), + query: filterConfig.getString('query'), + })), }; }); } else { @@ -78,6 +88,13 @@ export class JiraConfig { headers: parseHeaders(jira.getOptionalConfig(JIRA_CONFIG_HEADERS)), baseUrl: jira.getString(JIRA_CONFIG_BASE_URL), userEmailSuffix: jira.getOptionalString(JIRA_CONFIG_USER_EMAIL_SUFFIX), + defaultFilters: jira + .getOptionalConfigArray(JIRA_FILTERS) + ?.map(filterConfig => ({ + name: filterConfig.getString('name'), + shortName: filterConfig.getString('shortName'), + query: filterConfig.getString('query'), + })), }; } } @@ -136,4 +153,8 @@ export class JiraConfig { const instance = this.forInstance(instanceName); return instance.userEmailSuffix; } + resolveDefaultFilters(instanceName: string): Filter[] | undefined { + const instance = this.forInstance(instanceName); + return instance.defaultFilters; + } } diff --git a/plugins/jira-dashboard-backend/src/filters.test.ts b/plugins/jira-dashboard-backend/src/filters.test.ts index 2e227d1..e2d2570 100644 --- a/plugins/jira-dashboard-backend/src/filters.test.ts +++ b/plugins/jira-dashboard-backend/src/filters.test.ts @@ -1,4 +1,4 @@ -import { getDefaultFiltersForUser } from './filters'; +import { getAssigneUser, getDefaultFiltersForUser } from './filters'; import { mockServices } from '@backstage/backend-test-utils'; import { UserEntity } from '@backstage/catalog-model'; import { ConfigInstance, JiraConfig } from './config'; @@ -14,6 +14,18 @@ describe('getDefaultFiltersForUser', () => { }, }); const instance = JiraConfig.fromConfig(mockConfig).getInstance(); + const mockInstance: ConfigInstance = { + token: 'mock_token', + headers: {}, + baseUrl: 'http://jira.com', + defaultFilters: [ + { + name: 'My Open Bugs', + shortName: 'MyBugs', + query: 'type = Bug AND resolution = Unresolved', + }, + ], + }; const mockUserEntity: UserEntity = { apiVersion: 'backstage.io/v1beta1', @@ -61,4 +73,47 @@ describe('getDefaultFiltersForUser', () => { const filters = getDefaultFiltersForUser(instance); expect(filters).toHaveLength(2); }); + + it('should include defaultFilters from config', () => { + const filters = getDefaultFiltersForUser(mockInstance, mockUserEntity); + expect(filters).toContainEqual( + expect.objectContaining({ + name: 'My Open Bugs', + shortName: 'MyBugs', + query: 'type = Bug AND resolution = Unresolved', + }), + ); + }); + + it('should handle empty defaultFilters in config', () => { + const filters = getDefaultFiltersForUser(mockInstance, mockUserEntity); + expect(filters).toHaveLength(4); + }); + + it('should correctly apply filterOnUser logic', () => { + const filters = getDefaultFiltersForUser(mockInstance, mockUserEntity); + const expectedAssignee = getAssigneUser(mockInstance, mockUserEntity); + expect(filters).toEqual([ + expect.objectContaining({ + name: 'Open Issues', + query: 'resolution = Unresolved ORDER BY updated DESC', + shortName: 'OPEN', + }), + expect.objectContaining({ + name: 'Incoming Issues', + query: "status = 'New' ORDER BY created ASC", + shortName: 'INCOMING', + }), + expect.objectContaining({ + name: 'Assigned to me', + query: `assignee = "${expectedAssignee}" AND resolution = Unresolved ORDER BY updated DESC`, + shortName: 'ME', + }), + expect.objectContaining({ + name: 'My Open Bugs', + query: `type = Bug AND resolution = Unresolved`, + shortName: 'MyBugs', + }), + ]); + }); }); diff --git a/plugins/jira-dashboard-backend/src/filters.ts b/plugins/jira-dashboard-backend/src/filters.ts index da17655..15573af 100644 --- a/plugins/jira-dashboard-backend/src/filters.ts +++ b/plugins/jira-dashboard-backend/src/filters.ts @@ -52,9 +52,16 @@ export const getDefaultFiltersForUser = ( ): Filter[] => { const incomingFilter = getIncomingFilter(incomingStatus ?? 'New'); - if (!userEntity) return [openFilter, incomingFilter]; + const defaultFilters = + instance.defaultFilters?.map(filter => ({ + name: filter.name, + query: filter.query, + shortName: filter.shortName, + })) || []; + + if (!userEntity) return [openFilter, incomingFilter, ...defaultFilters]; const assigneeToMeFilter = getAssignedToMeFilter(userEntity, instance); - return [openFilter, incomingFilter, assigneeToMeFilter]; + return [openFilter, incomingFilter, assigneeToMeFilter, ...defaultFilters]; };