diff --git a/.changeset/famous-swans-smile.md b/.changeset/famous-swans-smile.md new file mode 100644 index 000000000..1a75f5f7d --- /dev/null +++ b/.changeset/famous-swans-smile.md @@ -0,0 +1,5 @@ +--- +'@roadiehq/backstage-plugin-argo-cd': minor +--- + +Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export. diff --git a/plugins/frontend/backstage-plugin-argo-cd/package.json b/plugins/frontend/backstage-plugin-argo-cd/package.json index 8d8ae3848..5fd6cd6c4 100644 --- a/plugins/frontend/backstage-plugin-argo-cd/package.json +++ b/plugins/frontend/backstage-plugin-argo-cd/package.json @@ -23,9 +23,25 @@ "role": "frontend-plugin", "pluginId": "backstage-plugin-argo-cd", "pluginPackages": [ - "@roadiehq/backstage-plugin-argo-cd" + "@roadiehq/backstage-plugin-argo-cd", + "@roadiehq/backstage-plugin-argo-cd-backend" ] }, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "sideEffects": false, "scripts": { "build": "backstage-cli package build", @@ -39,8 +55,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.6.0", + "@backstage/core-compat-api": "^0.2.8", "@backstage/core-components": "^0.14.10", "@backstage/core-plugin-api": "^1.9.3", + "@backstage/frontend-plugin-api": "^0.7.0", "@backstage/plugin-catalog-react": "^1.12.3", "@backstage/theme": "^0.5.6", "@material-ui/core": "^4.12.2", @@ -71,6 +89,7 @@ "@rollup/plugin-node-resolve": "^15.0.1", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", + "@backstage/frontend-test-utils": "^0.1.12", "esbuild": "^0.11.13", "jest-environment-jsdom": "^29.2.1", "msw": "^1.0.1", diff --git a/plugins/frontend/backstage-plugin-argo-cd/src/alpha.ts b/plugins/frontend/backstage-plugin-argo-cd/src/alpha.ts new file mode 100644 index 000000000..de7705b2d --- /dev/null +++ b/plugins/frontend/backstage-plugin-argo-cd/src/alpha.ts @@ -0,0 +1,2 @@ +export * from './alpha/index'; +export { default } from './alpha/index'; diff --git a/plugins/frontend/backstage-plugin-argo-cd/src/alpha/apis.tsx b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/apis.tsx new file mode 100644 index 000000000..9490f30f7 --- /dev/null +++ b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/apis.tsx @@ -0,0 +1,33 @@ +import { + createApiExtension, + createApiFactory, + discoveryApiRef, +} from '@backstage/frontend-plugin-api'; +import { ArgoCDApiClient, argoCDApiRef } from '../api'; +import { configApiRef, identityApiRef } from '@backstage/core-plugin-api'; + +/** + * @alpha + */ +export const argoCDApiExtension = createApiExtension({ + factory: createApiFactory({ + api: argoCDApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, identityApi, configApi }) => + new ArgoCDApiClient({ + discoveryApi, + identityApi, + backendBaseUrl: configApi.getString('backend.baseUrl'), + useNamespacedApps: Boolean( + configApi.getOptionalBoolean('argocd.namespacedApps'), + ), + searchInstances: Boolean( + configApi.getOptionalConfigArray('argocd.appLocatorMethods')?.length, + ), + }), + }), +}); diff --git a/plugins/frontend/backstage-plugin-argo-cd/src/alpha/entityCards.test.tsx b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/entityCards.test.tsx new file mode 100644 index 000000000..cf00b92f1 --- /dev/null +++ b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/entityCards.test.tsx @@ -0,0 +1,47 @@ +import { screen, waitFor } from '@testing-library/react'; +import { + createExtensionTester, + TestApiProvider, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { + entityArgoCDOverviewCard, + entityArgoCDHistoryCard, +} from './entityCards'; +import { ArgoCDApiClient, argoCDApiRef } from '../api'; +import { getEntityStub } from '../mocks/mocks'; +import React from 'react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; + +describe('Entity cards extensions', () => { + const mockArgocdApi = {} as unknown as ArgoCDApiClient; + const mockedEntity = getEntityStub; + + it('should render the overview card on an entity', async () => { + renderInTestApp( + + + {createExtensionTester(entityArgoCDOverviewCard).reactElement()} + + , + ); + + await waitFor(() => { + expect(screen.getByText('ArgoCD overview')).toBeInTheDocument(); + }); + }); + + it('should render the history card on an entity', async () => { + renderInTestApp( + + + {createExtensionTester(entityArgoCDHistoryCard).reactElement()} + + , + ); + + await waitFor(() => { + expect(screen.getByText('ArgoCD history')).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/frontend/backstage-plugin-argo-cd/src/alpha/entityCards.tsx b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/entityCards.tsx new file mode 100644 index 000000000..91cfaac1a --- /dev/null +++ b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/entityCards.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; + +/** + * @alpha + */ +export const entityArgoCDOverviewCard = EntityCardBlueprint.make({ + name: 'overviewCard', + params: { + filter: 'kind:component', + loader: () => + import('../components/ArgoCDDetailsCard').then(m => ( + + )), + }, +}); + +/** + * @alpha + */ +export const entityArgoCDHistoryCard: any = EntityCardBlueprint.make({ + name: 'historyCard', + params: { + filter: 'kind:component', + loader: () => + import('../components/ArgoCDHistoryCard').then(m => ( + + )), + }, +}); diff --git a/plugins/frontend/backstage-plugin-argo-cd/src/alpha/index.ts b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/index.ts new file mode 100644 index 000000000..b68aea57f --- /dev/null +++ b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/plugins/frontend/backstage-plugin-argo-cd/src/alpha/pages.tsx b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/pages.tsx new file mode 100644 index 000000000..e87f6060c --- /dev/null +++ b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/pages.tsx @@ -0,0 +1,22 @@ +import React from 'react'; // Add this line to import React + +import { createPageExtension } from '@backstage/frontend-plugin-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; +import { entityContentRouteRef } from '../plugin'; + +/** + * @alpha + */ +export const argoCdPage = createPageExtension({ + name: 'ArgoCdPage', + namespace: 'argocd', + defaultPath: '/', + // you can reuse the existing routeRef + // by wrapping into the convertLegacyRouteRef. + routeRef: convertLegacyRouteRef(entityContentRouteRef), + // these inputs usually match the props required by the component. + loader: () => import('../Router').then(m => compatWrapper()), +}); diff --git a/plugins/frontend/backstage-plugin-argo-cd/src/alpha/plugin.tsx b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/plugin.tsx new file mode 100644 index 000000000..f1cb46706 --- /dev/null +++ b/plugins/frontend/backstage-plugin-argo-cd/src/alpha/plugin.tsx @@ -0,0 +1,26 @@ +import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; +import { createPlugin, BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { + entityArgoCDOverviewCard, + entityArgoCDHistoryCard, +} from './entityCards'; +import { argoCDApiExtension } from './apis'; + +import { entityContentRouteRef } from '../plugin'; +import { argoCdPage } from './pages'; + +/** + * @alpha + */ +const plugin: BackstagePlugin = createPlugin({ + id: 'argocd', + extensions: [ + argoCdPage, + entityArgoCDOverviewCard, + entityArgoCDHistoryCard, + argoCDApiExtension, + ], + routes: convertLegacyRouteRefs({ argocd: entityContentRouteRef }), +}); + +export default plugin; diff --git a/plugins/frontend/backstage-plugin-argo-cd/src/components/ArgoCDDetailsCard.tsx b/plugins/frontend/backstage-plugin-argo-cd/src/components/ArgoCDDetailsCard.tsx index 3d0db4269..31b344eb2 100644 --- a/plugins/frontend/backstage-plugin-argo-cd/src/components/ArgoCDDetailsCard.tsx +++ b/plugins/frontend/backstage-plugin-argo-cd/src/components/ArgoCDDetailsCard.tsx @@ -32,7 +32,6 @@ import { import { ErrorBoundary, InfoCard, - MissingAnnotationEmptyState, Table, TableColumn, } from '@backstage/core-components'; @@ -41,7 +40,10 @@ import { isArgocdAvailable } from '../conditions'; import { ArgoCDAppDetails, ArgoCDAppList } from '../types'; import { useAppDetails } from './useAppDetails'; import SyncIcon from '@material-ui/icons/Sync'; -import { useEntity } from '@backstage/plugin-catalog-react'; +import { + MissingAnnotationEmptyState, + useEntity, +} from '@backstage/plugin-catalog-react'; import { DetailsDrawerComponent as detailsDrawerComponent } from './DetailsDrawer'; interface Condition { diff --git a/plugins/frontend/backstage-plugin-argo-cd/src/components/ArgoCDHistoryCard.tsx b/plugins/frontend/backstage-plugin-argo-cd/src/components/ArgoCDHistoryCard.tsx index f819e1843..a484d109b 100644 --- a/plugins/frontend/backstage-plugin-argo-cd/src/components/ArgoCDHistoryCard.tsx +++ b/plugins/frontend/backstage-plugin-argo-cd/src/components/ArgoCDHistoryCard.tsx @@ -15,13 +15,12 @@ */ import { Entity } from '@backstage/catalog-model'; +import { ErrorBoundary, InfoCard } from '@backstage/core-components'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; import { - ErrorBoundary, - InfoCard, MissingAnnotationEmptyState, -} from '@backstage/core-components'; -import { configApiRef, useApi } from '@backstage/core-plugin-api'; -import { useEntity } from '@backstage/plugin-catalog-react'; + useEntity, +} from '@backstage/plugin-catalog-react'; import { LinearProgress } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { isArgocdAvailable } from '../conditions'; diff --git a/yarn.lock b/yarn.lock index 687997bc0..41ab51f19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30572,16 +30572,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -30655,7 +30646,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -30669,13 +30660,6 @@ strip-ansi@5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -32801,7 +32785,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -32819,15 +32803,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"