Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins): ui for dynamic-plugins-info-backend #1138

Merged
merged 1 commit into from
Jan 31, 2024
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
1 change: 1 addition & 0 deletions plugins/dynamic-plugins-info/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
7 changes: 7 additions & 0 deletions plugins/dynamic-plugins-info/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Setting up the development environment for Dynamic Plugins Info plugin

In [Backstage plugin terminology](https://backstage.io/docs/local-dev/cli-build-system#package-roles), the Dynamic Plugins Info plugin is a front-end plugin. You can start a live development session from the repository root using the following command:

```console
yarn workspace @janus-idp/backstage-plugin-dynamic-plugins-info run start
```
45 changes: 45 additions & 0 deletions plugins/dynamic-plugins-info/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Dynamic Plugins Info plugin for Backstage

The dynamic-plugins-info plugin is a frontend component for the [dynamic-plugins-info-backend](https://github.com/janus-idp/backstage-showcase/tree/main/plugins/dynamic-plugins-info-backend) plugin. It offers a simple table UI that supports client-side sorting, filtering and pagination.

The plugin is designed to be installed dynamically in the [backstage-showcase](https://github.com/janus-idp/backstage-showcase/tree/main) app.

To build this plugin and the dynamic entrypoint:

`yarn install`

`yarn tsc`

`yarn build`

`yarn export-dynamic`

To install the dynamic plugin from a local build:

```bash
cd dist-scalprum
npm pack .
archive=$(npm pack $pkg)
tar -xzf "$archive" && rm "$archive"
mv package $(echo $archive | sed -e 's:\.tgz$::')
```

Move the resulting directory (`janus-idp-backstage-plugin-dynamic-plugins-info-0.1.0`) into the `dynamic-plugins-root` folder of your [backstage-showcase](https://github.com/janus-idp/backstage-showcase/tree/main) clone.

This configuration will enable the plugin to be visible in the UI:

```yaml
dynamicPlugins:
frontend:
janus-idp.backstage-plugin-dynamic-plugins-info:
dynamicRoutes:
- path: /admin/plugins
importName: DynamicPluginsInfo
mountPoints:
- mountPoint: admin.page.plugins/cards
importName: DynamicPluginsInfo
config:
layout:
gridColumn: '1 / -1'
width: 100vw
```
13 changes: 13 additions & 0 deletions plugins/dynamic-plugins-info/app-config.janus-idp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
dynamicPlugins:
frontend:
janus-idp.backstage-plugin-dynamic-plugins-info:
dynamicRoutes:
- path: /admin/plugins
importName: DynamicPluginsInfo
mountPoints:
- mountPoint: admin.page.plugins/cards
importName: DynamicPluginsInfo
config:
layout:
gridColumn: '1 / -1'
width: 100vw
85 changes: 85 additions & 0 deletions plugins/dynamic-plugins-info/dev/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';

import { Content, Header, HeaderTabs, Page } from '@backstage/core-components';
import { createDevApp } from '@backstage/dev-utils';
import { TestApiProvider } from '@backstage/test-utils';

import { dynamicPluginsInfoApiRef } from '../src/api/types';
import { DynamicPluginsInfoContent } from '../src/components/DynamicPluginsInfoContent/DynamicPluginsInfoContent';
import { dynamicPluginsInfoPlugin } from '../src/plugin';

export const listLoadedPluginsResult = [
{
name: 'some-plugin-one',
version: '0.1.0',
role: 'frontend-plugin',
platform: 'web',
},
{
name: 'some-plugin-two',
version: '1.1.0',
role: 'backend-plugin-module',
platform: 'node',
},
{
name: 'some-plugin-three',
version: '0.1.2',
role: 'backend-plugin',
platform: 'node',
},
{
name: 'some-plugin-four',
version: '1.1.0',
role: 'frontend-plugin',
platform: 'web',
},
{
name: 'some-plugin-five',
version: '1.2.0',
role: 'frontend-plugin',
platform: 'web',
},
{
name: 'some-plugin-six',
version: '0.6.3',
role: 'backend-plugin',
platform: 'node',
},
];

const mockedApi = {
listLoadedPlugins: async () => {
return listLoadedPluginsResult;
},
};

createDevApp()
.registerPlugin(dynamicPluginsInfoPlugin)
.addPage({
element: (
<TestApiProvider apis={[[dynamicPluginsInfoApiRef, mockedApi]]}>
<Page themeId="theme">
<Header title="Administration" />
<HeaderTabs
selectedIndex={1}
tabs={[
{
id: 'rbac',
label: 'RBAC',
},
{
id: 'plugins',
label: 'Plugins',
},
]}
/>
<Content>
<DynamicPluginsInfoContent />
</Content>
</Page>
</TestApiProvider>
),
title: 'Root Page',
path: '/dynamic-plugins-info',
})
.render();
65 changes: 65 additions & 0 deletions plugins/dynamic-plugins-info/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "@janus-idp/backstage-plugin-dynamic-plugins-info",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "frontend-plugin"
},
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"export-dynamic": "janus-cli package export-dynamic-plugin",
"lint": "backstage-cli package lint",
"postpack": "backstage-cli package postpack",
"postversion": "yarn run export-dynamic",
"prepack": "backstage-cli package prepack",
"start": "backstage-cli package start",
"test": "backstage-cli package test --passWithNoTests --coverage",
"tsc": "tsc"
},
"dependencies": {
"@backstage/core-components": "^0.13.6",
"@backstage/core-plugin-api": "^1.7.0",
"@backstage/theme": "^0.4.3",
"@material-table/core": "^3.1.0",
"react-use": "^17.4.0"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0",
"react-router-dom": "^6.20.0"
},
"devDependencies": {
"@backstage/cli": "0.23.0",
"@backstage/core-app-api": "1.11.0",
"@backstage/dev-utils": "1.0.22",
"@backstage/test-utils": "1.4.4",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "12.1.5",
"@testing-library/user-event": "14.5.1",
"msw": "1.3.2"
},
"files": [
"dist",
"dist-scalprum"
],
"scalprum": {
"name": "janus-idp.backstage-plugin-dynamic-plugins-info",
"exposedModules": {
"PluginRoot": "./src/index.ts"
}
},
"repository": "github:janus-idp/backstage-plugins",
"keywords": [
"backstage",
"plugin"
],
"homepage": "https://janus-idp.io/",
"bugs": "https://github.com/janus-idp/backstage-plugins/issues"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';

import { DynamicPluginInfo, DynamicPluginsInfoApi } from './types';

export interface DynamicPluginsInfoClientOptions {
discoveryApi: DiscoveryApi;
fetchApi: FetchApi;
}

const loadedPluginsEndpoint = '/loaded-plugins';

export class DynamicPluginsInfoClient implements DynamicPluginsInfoApi {
private readonly discoveryApi: DiscoveryApi;
private readonly fetchApi: FetchApi;

constructor(options: DynamicPluginsInfoClientOptions) {
this.discoveryApi = options.discoveryApi;
this.fetchApi = options.fetchApi;
}
async listLoadedPlugins(): Promise<DynamicPluginInfo[]> {
const baseUrl = await this.discoveryApi.getBaseUrl('dynamic-plugins-info');
const targetUrl = `${baseUrl}${loadedPluginsEndpoint}`;
const response = await this.fetchApi.fetch(targetUrl);
const data = await response.json();
if (!response.ok) {
throw new Error(`${data.message}`);
}
return data;
}
}
16 changes: 16 additions & 0 deletions plugins/dynamic-plugins-info/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createApiRef } from '@backstage/core-plugin-api';

export type DynamicPluginInfo = {
name: string;
version: string;
role: string;
platform: string;
};

export interface DynamicPluginsInfoApi {
listLoadedPlugins(): Promise<DynamicPluginInfo[]>;
}

export const dynamicPluginsInfoApiRef = createApiRef<DynamicPluginsInfoApi>({
id: 'plugin.dynamic-plugins-info',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

import { ContentHeader, SupportButton } from '@backstage/core-components';

import { DynamicPluginsTable } from '../DynamicPluginsTable/DynamicPluginsTable';

export const DynamicPluginsInfoContent = () => (
<>
<ContentHeader title="">
<SupportButton title="Support">Some placeholder text</SupportButton>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@invincibleJai I wonder if I should just take this out at this time, I don't see this being used in other plugins currently

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah let's open an issue to handle it separately if needed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #1150 for this

</ContentHeader>
<DynamicPluginsTable />
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useState } from 'react';

import {
ResponseErrorPanel,
Table,
TableColumn,
} from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';

import { Query, QueryResult } from '@material-table/core';

import { DynamicPluginInfo, dynamicPluginsInfoApiRef } from '../../api/types';

export const DynamicPluginsTable = () => {
const [error, setError] = useState<Error | undefined>(undefined);
const [count, setCount] = useState<number>(0);
const dynamicPluginInfo = useApi(dynamicPluginsInfoApiRef);
const columns: TableColumn<DynamicPluginInfo>[] = [
{
title: 'Name',
field: 'name',
defaultSort: 'asc',
},
{
title: 'Version',
field: 'version',
width: '30%',
},
{
title: 'Role',
render: ({ platform, role }) => <>{`${role} (${platform})`}</>,
sorting: false,
},
];
const fetchData = async (
query: Query<DynamicPluginInfo>,
): Promise<QueryResult<DynamicPluginInfo>> => {
const {
orderBy = { field: 'name' },
orderDirection = 'asc',
page = 0,
pageSize = 5,
search = '',
} = query || {};
try {
// for now sorting/searching/pagination is handled client-side
const data = (await dynamicPluginInfo.listLoadedPlugins())
.sort((a: Record<string, string>, b: Record<string, string>) => {
const field = orderBy.field!;
if (!a[field] || !b[field]) {
return 0;
}
return (
a[field].localeCompare(b[field]) *
(orderDirection === 'desc' ? -1 : 1)
);
})
.filter(
value =>
search.trim() === '' ||
JSON.stringify(value).indexOf(search.trim()) > 0,
);
const totalCount = data.length;
let start = 0;
let end = totalCount;
if (totalCount > pageSize) {
start = page * pageSize;
end = start + pageSize;
}
setCount(totalCount);
return { data: data.slice(start, end), page, totalCount };
} catch (loadingError) {
setError(loadingError as Error);
return { data: [], totalCount: 0, page: 0 };
}
};
if (error) {
return <ResponseErrorPanel error={error} />;
}
return (
<Table
title={`Installed Plugins (${count})`}
options={{
draggable: false,
filtering: false,
sorting: true,
paging: true,
thirdSortClick: false,
debounceInterval: 500,
}}
columns={columns}
data={fetchData}
/>
);
};
1 change: 1 addition & 0 deletions plugins/dynamic-plugins-info/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { dynamicPluginsInfoPlugin, DynamicPluginsInfo } from './plugin';
7 changes: 7 additions & 0 deletions plugins/dynamic-plugins-info/src/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { dynamicPluginsInfoPlugin } from './plugin';

describe('dynamic-plugins-info', () => {
it('should export plugin', () => {
expect(dynamicPluginsInfoPlugin).toBeDefined();
});
});
Loading
Loading