Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fuzzy-ravens-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@axis-backstage/plugin-jira-dashboard': minor
---

Add a configurable Jira user issues homepage widget for the new frontend system and isolate the alpha entrypoint from legacy frontend APIs.
30 changes: 30 additions & 0 deletions plugins/jira-dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,36 @@ app:
- entity-content:jira-dashboard/entity
```

The plugin also contributes a `home-page-widget:jira-dashboard/user-issues`
extension to the new frontend system's Home plugin. With feature discovery it
is available automatically. Its legacy card options can be configured in
`app-config.yaml`:

```yaml
app:
extensions:
- home-page-widget:jira-dashboard/user-issues:
config:
title: My Jira Issues
maxResults: 30
filterName: Unresolved
bottomLink:
link: https://our-jira-server/issues
title: Open in Jira
tableOptions:
toolbar: true
search: true
paging: true
pageSize: 15
tableStyle:
padding: 5px
overflowY: auto
width: 95%
```

The Home plugin must be installed in the app for the widget to have a homepage
attachment point.

## Layout

The issue overview is located under the tab "Jira Dashboard" on the entity page. The overview displays information about the specific Jira project, and then renders one table for each type of issue view. In each view you can see the priority, assignee and status for that issue.
Expand Down
5 changes: 3 additions & 2 deletions plugins/jira-dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,19 @@
"dependencies": {
"@axis-backstage/plugin-jira-dashboard-common": "workspace:^",
"@backstage/catalog-model": "^1.9.0",
"@backstage/core-compat-api": "^0.5.13",
"@backstage/core-components": "^0.18.12",
"@backstage/core-plugin-api": "^1.12.8",
"@backstage/errors": "^1.3.1",
"@backstage/frontend-plugin-api": "^0.17.3",
"@backstage/plugin-catalog-react": "^3.2.0",
"@backstage/plugin-home-react": "^0.1.40",
"@backstage/theme": "^0.7.3",
"@mui/material": "^5.15.7",
"@mui/styles": "^5.15.7",
"lodash": "^4.18.1",
"luxon": "^3.5.0",
"react-use": "^17.2.4"
"react-use": "^17.2.4",
"zod": "^4.3.6"
},
"peerDependencies": {
"@material-ui/core": "^4.12.2",
Expand Down
57 changes: 57 additions & 0 deletions plugins/jira-dashboard/report-alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { Entity } from '@backstage/catalog-model';
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { FilterPredicate } from '@backstage/filter-predicates';
import { HomePageWidgetBlueprintParams } from '@backstage/plugin-home-react/alpha';
import { HomePageWidgetData } from '@backstage/plugin-home-react/alpha';
import { JSX as JSX_2 } from 'react';
import { JSXElementConstructor } from 'react';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
Expand Down Expand Up @@ -118,6 +120,61 @@ const _default: OverridableFrontendPlugin<
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
};
}>;
'home-page-widget:jira-dashboard/user-issues': OverridableExtensionDefinition<{
config: {
title: string;
maxResults: number;
filterName: string;
bottomLink:
| {
link: string;
title: string;
}
| undefined;
tableOptions: {
toolbar: boolean;
search: boolean;
paging: boolean;
pageSize: number;
};
tableStyle: {
padding: string;
overflowY: 'auto' | 'hidden' | 'visible' | 'scroll';
width: string;
};
};
configInput: {
title?: string | undefined;
maxResults?: number | undefined;
filterName?: string | undefined;
bottomLink?:
| {
link: string;
title?: string | undefined;
}
| undefined;
tableOptions?:
| {
toolbar?: boolean | undefined;
search?: boolean | undefined;
paging?: boolean | undefined;
pageSize?: number | undefined;
}
| undefined;
tableStyle?:
| {
padding?: string | undefined;
overflowY?: 'auto' | 'hidden' | 'visible' | 'scroll' | undefined;
width?: string | undefined;
}
| undefined;
};
output: ExtensionDataRef<HomePageWidgetData, 'home.widget.data', {}>;
inputs: {};
kind: 'home-page-widget';
name: 'user-issues';
params: HomePageWidgetBlueprintParams;
}>;
}
>;
export default _default;
Expand Down
6 changes: 3 additions & 3 deletions plugins/jira-dashboard/report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { ApiRef } from '@backstage/frontend-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { BottomLinkProps } from '@backstage/core-components';
import { CSSProperties } from 'react';
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { FetchApi } from '@backstage/core-plugin-api';
import type { DiscoveryApi } from '@backstage/core-plugin-api';
import type { Entity } from '@backstage/catalog-model';
import type { FetchApi } from '@backstage/core-plugin-api';
import { Issue } from '@axis-backstage/plugin-jira-dashboard-common';
import { JiraResponse } from '@axis-backstage/plugin-jira-dashboard-common';
import { JSX as JSX_2 } from 'react/jsx-runtime';
Expand Down
15 changes: 9 additions & 6 deletions plugins/jira-dashboard/src/alpha.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { convertLegacyRouteRefs } from '@backstage/core-compat-api';
import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
import { entityJiraContent, jiraApi } from './alpha/index';
import { rootRouteRef } from './routes';
import {
entityJiraContent,
jiraApi,
jiraUserIssuesWidget,
} from './alpha/index';
import { rootRouteRef } from './alpha/routes';

/**
* Frontend plugin that fetches and displays Jira issues for an entity
Expand All @@ -11,8 +14,8 @@ import { rootRouteRef } from './routes';
*/
export default createFrontendPlugin({
pluginId: 'jira-dashboard',
extensions: [entityJiraContent, jiraApi],
routes: convertLegacyRouteRefs({
extensions: [entityJiraContent, jiraApi, jiraUserIssuesWidget],
routes: {
rootRouteRef,
}),
},
});
19 changes: 7 additions & 12 deletions plugins/jira-dashboard/src/alpha/entityContent.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import {
compatWrapper,
convertLegacyRouteRef,
} from '@backstage/core-compat-api';
import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha';
import { rootRouteRef } from '../routes';

import { configApiRef } from '@backstage/core-plugin-api';
import { isJiraDashboardAvailable } from '..';
import { configApiRef } from '@backstage/frontend-plugin-api';
import { isJiraDashboardAvailable } from '../availability';
import { rootRouteRef } from './routes';
/**
* @alpha
*/
Expand All @@ -21,11 +16,11 @@ export const entityJiraContent = EntityContentBlueprint.makeWithOverrides({
path: '/jira',
title: 'Jira Dashboard',
filter: entity => isJiraDashboardAvailable(entity, annotationPrefix),
routeRef: convertLegacyRouteRef(rootRouteRef),
routeRef: rootRouteRef,
loader: async () =>
import('../components/JiraDashboardContent').then(m =>
compatWrapper(<m.JiraDashboardContent />),
),
import('../components/JiraDashboardContent').then(m => (
<m.JiraDashboardContent />
)),
});
},
});
59 changes: 59 additions & 0 deletions plugins/jira-dashboard/src/alpha/homePageWidget.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { screen, waitFor } from '@testing-library/react';
import {
createExtensionTester,
renderInTestApp,
} from '@backstage/frontend-test-utils';
import { homePageWidgetDataRef } from '@backstage/plugin-home-react/alpha';
import { JiraDashboardApi, jiraDashboardApiRef } from '../api';
import { jiraUserIssuesWidget } from './homePageWidget';

const mockJiraApi = {
getLoggedInUserIssues: jest.fn().mockResolvedValue([]),
} as unknown as JiraDashboardApi;

describe('Jira user issues homepage widget', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('registers and renders a configurable homepage widget', async () => {
const tester = createExtensionTester(jiraUserIssuesWidget, {
config: {
title: 'Team Jira Issues',
maxResults: 25,
filterName: 'Unresolved',
bottomLink: {
link: 'https://jira.example.com/issues',
title: 'Open Jira',
},
},
});
const widget = tester.get(homePageWidgetDataRef);

expect(widget.name).toBe('JiraUserIssuesViewCard');
expect(widget.title).toBe('Team Jira Issues');
expect(widget.layout).toEqual({
width: { minColumns: 4, defaultColumns: 8 },
height: { minRows: 4 },
});

renderInTestApp(widget.component, {
apis: [[jiraDashboardApiRef, mockJiraApi]],
});

expect(
await screen.findByText('Team Jira Issues', {}, { timeout: 5000 }),
).toBeInTheDocument();
expect(
await screen.findByText('Open Jira', {}, { timeout: 5000 }),
).toBeInTheDocument();
await waitFor(
() =>
expect(mockJiraApi.getLoggedInUserIssues).toHaveBeenCalledWith(
25,
'Unresolved',
),
{ timeout: 5000 },
);
});
});
83 changes: 83 additions & 0 deletions plugins/jira-dashboard/src/alpha/homePageWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { HomePageWidgetBlueprint } from '@backstage/plugin-home-react/alpha';
import { z } from 'zod';

/**
* Homepage widget that displays Jira issues assigned to the signed-in user.
*
* @alpha
*/
export const jiraUserIssuesWidget = HomePageWidgetBlueprint.makeWithOverrides({
name: 'user-issues',
configSchema: {
title: z.string().default('My Jira Issues'),
maxResults: z.number().int().positive().default(15),
filterName: z.string().default('default'),
bottomLink: z
.object({
link: z.string(),
title: z.string().default('Open in Jira'),
})
.optional(),
tableOptions: z
.object({
toolbar: z.boolean().default(false),
search: z.boolean().default(false),
paging: z.boolean().default(true),
pageSize: z.number().int().positive().default(10),
})
.default({
toolbar: false,
search: false,
paging: true,
pageSize: 10,
}),
tableStyle: z
.object({
padding: z.string().default('0px'),
overflowY: z
.enum(['auto', 'hidden', 'scroll', 'visible'])
.default('auto'),
width: z.string().default('100%'),
})
.default({
padding: '0px',
overflowY: 'auto',
width: '100%',
}),
},
factory(originalFactory, { config }) {
const bottomLink = config.bottomLink;

return originalFactory({
name: 'JiraUserIssuesViewCard',
title: config.title,
description: 'Shows Jira issues assigned to the signed-in user',
components: async () => {
const [{ JiraUserIssuesTable }, { BottomLink }] = await Promise.all([
import('../components/JiraUserIssuesTable'),
import('@backstage/core-components'),
]);

return {
Content: props => <JiraUserIssuesTable {...props} />,
...(bottomLink
? {
Actions: () => <BottomLink {...bottomLink} />,
}
: {}),
};
},
componentProps: {
title: '',
maxResults: config.maxResults,
filterName: config.filterName,
tableOptions: config.tableOptions,
tableStyle: config.tableStyle,
},
layout: {
width: { minColumns: 4, defaultColumns: 8 },
height: { minRows: 4 },
},
});
},
});
1 change: 1 addition & 0 deletions plugins/jira-dashboard/src/alpha/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './apis';
export * from './entityContent';
export * from './homePageWidget';
4 changes: 4 additions & 0 deletions plugins/jira-dashboard/src/alpha/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createRouteRef } from '@backstage/frontend-plugin-api';

/** @alpha */
export const rootRouteRef = createRouteRef();
4 changes: 2 additions & 2 deletions plugins/jira-dashboard/src/api/JiraDashboardClient.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
import type { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
import {
Issue,
JiraResponse,
} from '@axis-backstage/plugin-jira-dashboard-common';
import { JiraDashboardApi } from './JiraDashboardApi';
import type { JiraDashboardApi } from './JiraDashboardApi';
import { ResponseError } from '@backstage/errors';
import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model';

Expand Down
18 changes: 18 additions & 0 deletions plugins/jira-dashboard/src/availability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Entity } from '@backstage/catalog-model';
import { PROJECT_KEY_NAME } from '@axis-backstage/plugin-jira-dashboard-common';

/**
* Checks if the entity has a jira.com project-key annotation.
*
* @public
* @param entity - The entity to check for the jira.com project-key annotation.
*/
export const isJiraDashboardAvailable = (
entity: Entity,
annotationPrefix?: string,
) =>
Boolean(
entity.metadata.annotations?.[
`${annotationPrefix ?? 'jira.com'}/${PROJECT_KEY_NAME}`
],
);
Loading
Loading