Skip to content

Commit

Permalink
Support for user defined additional filters
Browse files Browse the repository at this point in the history
  • Loading branch information
SaachiNayyer committed Nov 25, 2024
1 parent 6939770 commit bba9cdd
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 3 deletions.
8 changes: 8 additions & 0 deletions .changeset/two-students-care.md
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
22 changes: 22 additions & 0 deletions plugins/jira-dashboard-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions plugins/jira-dashboard-backend/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -13,6 +14,7 @@ export type ConfigInstance = {
headers: Record<string, string>;
baseUrl: string;
userEmailSuffix?: string;
defaultFilters?: Filter[];
};

// @public
Expand Down
18 changes: 18 additions & 0 deletions plugins/jira-dashboard-backend/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}[];
}[];
};
}
44 changes: 44 additions & 0 deletions plugins/jira-dashboard-backend/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"',
},
]);
});
});
21 changes: 21 additions & 0 deletions plugins/jira-dashboard-backend/src/config.ts
Original file line number Diff line number Diff line change
@@ -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<RootConfigService['getConfig']>;

Expand All @@ -25,13 +26,15 @@ export type ConfigInstance = {
headers: Record<string, string>;
baseUrl: string;
userEmailSuffix?: string;
defaultFilters?: Filter[];
};

const JIRA_CONFIG_BASE_URL = 'baseUrl';
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
Expand Down Expand Up @@ -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 {
Expand All @@ -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'),
})),
};
}
}
Expand Down Expand Up @@ -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;
}
}
57 changes: 56 additions & 1 deletion plugins/jira-dashboard-backend/src/filters.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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',
Expand Down Expand Up @@ -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',
}),
]);
});
});
11 changes: 9 additions & 2 deletions plugins/jira-dashboard-backend/src/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};

0 comments on commit bba9cdd

Please sign in to comment.