diff --git a/.changeset/rude-sloths-cross.md b/.changeset/rude-sloths-cross.md new file mode 100644 index 0000000000..8e22070662 --- /dev/null +++ b/.changeset/rude-sloths-cross.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-explore': patch +'@backstage/plugin-explore-backend': patch +'@backstage/plugin-explore-common': patch +'@backstage/plugin-explore-react': patch +--- + +Updated to use new @backstage/plugin-explore-backend diff --git a/packages/app/src/components/search/SearchModal.tsx b/packages/app/src/components/search/SearchModal.tsx index 218ead9b82..2aed970e75 100644 --- a/packages/app/src/components/search/SearchModal.tsx +++ b/packages/app/src/components/search/SearchModal.tsx @@ -25,6 +25,7 @@ import { Paper, useTheme, } from '@material-ui/core'; +import BuildIcon from '@material-ui/icons/Build'; import LaunchIcon from '@material-ui/icons/Launch'; import { CatalogIcon, @@ -38,6 +39,7 @@ import { catalogApiRef, CATALOG_FILTER_EXISTS, } from '@backstage/plugin-catalog-react'; +import { ToolSearchResultListItem } from '@backstage/plugin-explore'; import { searchPlugin, SearchType } from '@backstage/plugin-search'; import { DefaultResultListItem, @@ -110,6 +112,10 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => { value: 'techdocs', name: 'Documentation', }, + { + value: 'tools', + name: 'Tools', + }, ]} /> @@ -209,6 +215,17 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => { /> ); break; + case 'tools': + resultItem = ( + } + key={document.location} + result={document} + highlight={highlight} + rank={rank} + /> + ); + break; default: resultItem = ( ({ bar: { @@ -155,6 +157,16 @@ const SearchPage = () => { rank={rank} /> ); + case 'tools': + return ( + } + key={document.location} + result={document} + highlight={highlight} + rank={rank} + /> + ); default: return ( createEnv('permission')); const playlistEnv = useHotMemoize(module, () => createEnv('playlist')); const eventsEnv = useHotMemoize(module, () => createEnv('events')); + const exploreEnv = useHotMemoize(module, () => createEnv('explore')); const eventBasedEntityProviders = await catalogEventBasedProviders( catalogEnv, @@ -172,6 +174,7 @@ async function main() { apiRouter.use('/jenkins', await jenkins(jenkinsEnv)); apiRouter.use('/permission', await permission(permissionEnv)); apiRouter.use('/playlist', await playlist(playlistEnv)); + apiRouter.use('/explore', await explore(exploreEnv)); apiRouter.use(notFoundHandler()); const service = createServiceBuilder(module) diff --git a/packages/backend/src/plugins/explore.ts b/packages/backend/src/plugins/explore.ts new file mode 100644 index 0000000000..6793213dd3 --- /dev/null +++ b/packages/backend/src/plugins/explore.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createRouter, + getExampleTools, +} from '@backstage/plugin-explore-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + logger: env.logger, + toolProvider: { + getTools: getExampleTools, + }, + }); +} diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index 084b1a54e5..0e6982545f 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -16,6 +16,7 @@ import { useHotCleanup } from '@backstage/backend-common'; import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +import { ToolDocumentCollatorFactory } from '@backstage/plugin-explore-backend'; import { createRouter } from '@backstage/plugin-search-backend'; import { ElasticSearchSearchEngine } from '@backstage/plugin-search-backend-module-elasticsearch'; import { PgSearchEngine } from '@backstage/plugin-search-backend-module-pg'; @@ -84,6 +85,14 @@ export default async function createPlugin( }), }); + indexBuilder.addCollator({ + schedule, + factory: ToolDocumentCollatorFactory.fromConfig(env.config, { + discovery: env.discovery, + logger: env.logger, + }), + }); + // The scheduler controls when documents are gathered from collators and sent // to the search engine for indexing. const { scheduler } = await indexBuilder.build(); diff --git a/plugins/explore-backend/.eslintrc.js b/plugins/explore-backend/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/explore-backend/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/explore-backend/README.md b/plugins/explore-backend/README.md new file mode 100644 index 0000000000..ed065c19a4 --- /dev/null +++ b/plugins/explore-backend/README.md @@ -0,0 +1,93 @@ +# explore-backend + +Welcome to the explore-backend backend plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +### Install the Package + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/plugin-explore-backend +``` + +### Create a ToolProvider + +Create a `ToolProvider` to provide `ExploreTool` objects. + +```ts +import { + GetExploreToolsRequest, + GetExploreToolsResponse, +} from '@backstage/plugin-explore-common'; + +export async function getExploreTools( + request: GetExploreToolsRequest, +): Promise { + const { filter } = request ?? {}; + + // Get tool data from a file or some other source + const toolData = getToolData(); + + // Populate ExploreTool[] objects & use filter to filter them + const tools = [...toolData].filter(filterToolPredicate); + + return { + tools, + }; +} +``` + +**NOTE: You can see the `plugins/explore-backend/src/example/getExampleTools.ts` +as a simple hardcoded example.** + +### Adding the plugin to your `packages/backend` + +You'll need to add the plugin to the router in your `backend` package. You can +do this by creating a file called `packages/backend/src/plugins/explore.ts` + +```ts +import { + createRouter, + getExampleTools, +} from '@backstage/plugin-explore-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; +import { getExploreTools } from '../data/explore'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + logger: env.logger, + toolProvider: { + getTools: getExploreTools, + }, + }); +} +``` + +With the `explore.ts` router setup in place, add the router to +`packages/backend/src/index.ts`: + +```diff ++import explore from './plugins/explore'; + +async function main() { + ... + const createEnv = makeCreateEnv(config); + + const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); ++ const exploreEnv = useHotMemoize(module, () => createEnv('explore')); + + const apiRouter = Router(); ++ apiRouter.use('/explore', await explore(exploreEnv)); + ... + apiRouter.use(notFoundHandler()); +``` + +### Wire up the Frontend + +See [the explore plugin README](../explore/README.md) for more information. diff --git a/plugins/explore-backend/api-report.md b/plugins/explore-backend/api-report.md new file mode 100644 index 0000000000..7ab37bb598 --- /dev/null +++ b/plugins/explore-backend/api-report.md @@ -0,0 +1,59 @@ +## API Report File for "@backstage/plugin-explore-backend" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { Config } from '@backstage/config'; +import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; +import { ExploreTool } from '@backstage/plugin-explore-common'; +import express from 'express'; +import { GetExploreToolsRequest } from '@backstage/plugin-explore-common'; +import { GetExploreToolsResponse } from '@backstage/plugin-explore-common'; +import { IndexableDocument } from '@backstage/plugin-search-common'; +import { Logger } from 'winston'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { Readable } from 'stream'; + +// Warning: (ae-forgotten-export) The symbol "RouterOptions" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function createRouter(options: RouterOptions): Promise; + +// @public (undocumented) +export interface ExploreToolProvider { + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-explore-backend" does not have an export "GetExploreToolsRequest" + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-explore-backend" does not have an export "GetExploreToolsResponse" + getTools(request: GetExploreToolsRequest): Promise; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration +// Warning: (ae-missing-release-tag) "getExampleTools" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export function getExampleTools( + request: GetExploreToolsRequest, +): Promise; + +// @public +export class ToolDocumentCollatorFactory implements DocumentCollatorFactory { + // Warning: (ae-forgotten-export) The symbol "ToolDocument" needs to be exported by the entry point index.d.ts + // + // (undocumented) + execute(): AsyncGenerator; + // Warning: (ae-forgotten-export) The symbol "ToolDocumentCollatorFactoryOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + static fromConfig( + _config: Config, + options: ToolDocumentCollatorFactoryOptions, + ): ToolDocumentCollatorFactory; + // (undocumented) + getCollator(): Promise; + // (undocumented) + readonly type: string; +} +``` diff --git a/plugins/explore-backend/package.json b/plugins/explore-backend/package.json new file mode 100644 index 0000000000..17072e261e --- /dev/null +++ b/plugins/explore-backend/package.json @@ -0,0 +1,46 @@ +{ + "name": "@backstage/plugin-explore-backend", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/plugin-explore-common": "workspace:^", + "@backstage/plugin-search-common": "workspace:^", + "@types/express": "*", + "express": "^4.18.1", + "express-promise-router": "^4.1.0", + "lodash": "^4.17.21", + "node-fetch": "^2.6.7", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@types/supertest": "^2.0.8", + "msw": "^0.47.0", + "supertest": "^6.2.4" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/explore-backend/src/example/getExampleTools.ts b/plugins/explore-backend/src/example/getExampleTools.ts new file mode 100644 index 0000000000..8d26c8073a --- /dev/null +++ b/plugins/explore-backend/src/example/getExampleTools.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + GetExploreToolsRequest, + GetExploreToolsResponse, +} from '@backstage/plugin-explore-common'; +import { intersection, isEmpty } from 'lodash'; +import { exampleTools } from './tools'; + +const anyOf = (prop: T | T[], matches: T[]) => + isEmpty(matches) + ? true + : intersection([...[prop]].flat(), matches)?.length > 0; + +/** + * @private Example only - do not use in production + */ +export async function getExampleTools( + request: GetExploreToolsRequest, +): Promise { + const { filter } = request ?? {}; + const tags = filter?.tags ?? []; + const lifecycles = filter?.lifecycle ?? []; + + return { + tools: exampleTools.filter( + t => anyOf(t.tags ?? [], tags) && anyOf(t.lifecycle ?? [], lifecycles), + ), + }; +} diff --git a/plugins/explore-backend/src/example/tools.ts b/plugins/explore-backend/src/example/tools.ts new file mode 100644 index 0000000000..3726e4c5dd --- /dev/null +++ b/plugins/explore-backend/src/example/tools.ts @@ -0,0 +1,98 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExploreTool } from '@backstage/plugin-explore-common'; + +/** + * @private Example only - do not use in production + */ +export const exampleTools: ExploreTool[] = [ + { + title: 'New Relic', + description: + 'Observability platform built to help engineers create and monitor their software', + url: '/newrelic', + image: 'https://i.imgur.com/L37ikrX.jpg', + tags: ['newrelic', 'performance', 'monitoring', 'errors', 'alerting'], + }, + { + title: 'CircleCI', + description: + 'Provides builds overview, detailed build info and retriggering functionality for CircleCI.', + url: '/circleci', + image: 'https://miro.medium.com/max/1200/1*hkTBp22vLAqlIHkrkZHPnw.png', + tags: ['circleci', 'ci', 'dev'], + }, + { + title: 'Sentry', + description: + 'Self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time.', + url: '/sentry', + image: 'https://sentry-brand.storage.googleapis.com/sentry-logo-black.png', + tags: ['sentry', 'monitoring', 'errors'], + }, + { + title: 'Lighthouse', + description: + "Google's Lighthouse tool is a great resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your website.", + url: '/lighthouse', + image: + 'https://raw.githubusercontent.com/GoogleChrome/lighthouse/8b3d7f052b2e64dd857e741d7395647f487697e7/assets/lighthouse-logo.png', + tags: ['web', 'seo', 'accessibility', 'performance'], + }, + { + title: 'Tech Radar', + description: + 'Tech Radar is a list of technologies, complemented by an assessment result, called ring assignment.', + url: '/tech-radar', + image: + 'https://storage.googleapis.com/wf-blogs-engineering-media/2018/09/fe13bb32-wf-tech-radar-hero-1024x597.png', + tags: ['standards', 'landscape'], + }, + { + title: 'Cost Insights', + description: 'Insights into cloud costs for your organization.', + url: '/cost-insights', + image: 'https://cloud.google.com/images/press/logo-cloud.png', + tags: ['cloud', 'finops'], + }, + { + title: 'GraphiQL', + description: + 'Integrates GraphiQL as a tool to browse GraphiQL endpoints inside Backstage.', + url: '/graphiql', + image: + 'https://camo.githubusercontent.com/517398c3fbe0687d3d4dcbe05da82970b882e75a/68747470733a2f2f64337676366c703535716a6171632e636c6f756466726f6e742e6e65742f6974656d732f33413061324e314c3346324f304c3377326e316a2f477261706869514c382e706e673f582d436c6f75644170702d56697369746f722d49643d3433363432', + tags: ['graphql', 'dev'], + }, + { + title: 'GitOps Clusters', + description: + 'Create GitOps-managed clusters with Backstage. Currently supports EKS flavors and profiles like Machine Learning Ops (MLOps)', + url: '/gitops-clusters', + image: 'https://miro.medium.com/max/801/1*R28u8gj-hVdDFISoYqPhrQ.png', + tags: ['gitops', 'dev'], + }, + { + title: 'Rollbar', + description: + 'Error monitoring and crash reporting for agile development and continuous delivery', + url: '/rollbar', + image: + 'https://images.ctfassets.net/cj4mgtttlyx7/4DfiWj9CbuHBi10uWK7JHn/5e94a6c5dbd5d50bdcd8d9e78f88689b/rollbar-seo.png', + tags: ['rollbar', 'monitoring', 'errors'], + }, +]; diff --git a/plugins/explore-backend/src/index.ts b/plugins/explore-backend/src/index.ts new file mode 100644 index 0000000000..af9347377d --- /dev/null +++ b/plugins/explore-backend/src/index.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A Backstage plugin for building an exploration page of your software ecosystem + * + * @packageDocumentation + */ + +export * from './search'; +export { createRouter } from './service/router'; +export * from './service/types'; + +/** + * @private Example only - do not use in production + */ +export { getExampleTools } from './example/getExampleTools'; diff --git a/plugins/explore-backend/src/run.ts b/plugins/explore-backend/src/run.ts new file mode 100644 index 0000000000..d945aa13f0 --- /dev/null +++ b/plugins/explore-backend/src/run.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getRootLogger } from '@backstage/backend-common'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; + +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/explore-backend/src/search/ToolDocumentCollatorFactory.ts b/plugins/explore-backend/src/search/ToolDocumentCollatorFactory.ts new file mode 100644 index 0000000000..895eae43be --- /dev/null +++ b/plugins/explore-backend/src/search/ToolDocumentCollatorFactory.ts @@ -0,0 +1,99 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + IndexableDocument, + DocumentCollatorFactory, +} from '@backstage/plugin-search-common'; +import { Config } from '@backstage/config'; +import { Readable } from 'stream'; +import { Logger } from 'winston'; +import { ExploreTool } from '@backstage/plugin-explore-common'; +import fetch from 'node-fetch'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; + +/** + * Extended IndexableDocument with explore tool specific properties + * + * @public + */ +export interface ToolDocument extends IndexableDocument, ExploreTool {} + +/** + * The options for the {@link CatalogClient.ToolDocumentCollatorFactory}. + */ +type ToolDocumentCollatorFactoryOptions = { + discovery: PluginEndpointDiscovery; + logger: Logger; +}; + +/** + * Search collator responsible for collecting explore tools to index. + * + * @public + */ +export class ToolDocumentCollatorFactory implements DocumentCollatorFactory { + public readonly type: string = 'tools'; + + private readonly discovery: PluginEndpointDiscovery; + private readonly logger: Logger; + + private constructor(options: ToolDocumentCollatorFactoryOptions) { + this.discovery = options.discovery; + this.logger = options.logger; + } + + static fromConfig( + _config: Config, + options: ToolDocumentCollatorFactoryOptions, + ) { + return new ToolDocumentCollatorFactory(options); + } + + async getCollator() { + return Readable.from(this.execute()); + } + + async *execute(): AsyncGenerator { + this.logger.info('Starting collation of explore tools'); + + const tools = await this.fetchTools(); + + for (const tool of tools) { + yield { + ...tool, + text: tool.description, + location: tool.url, + }; + } + + this.logger.info('Finished collation of explore tools'); + } + + private async fetchTools() { + const baseUrl = await this.discovery.getBaseUrl('explore'); + const response = await fetch(`${baseUrl}/tools`); + + if (!response.ok) { + throw new Error( + `Failed to explore fetch tools, ${response.status}: ${response.statusText}`, + ); + } + + const data = await response.json(); + return data.tools; + } +} diff --git a/plugins/explore-backend/src/search/index.ts b/plugins/explore-backend/src/search/index.ts new file mode 100644 index 0000000000..9201518a3d --- /dev/null +++ b/plugins/explore-backend/src/search/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ToolDocumentCollatorFactory } from './ToolDocumentCollatorFactory'; diff --git a/plugins/explore-backend/src/service/router.test.ts b/plugins/explore-backend/src/service/router.test.ts new file mode 100644 index 0000000000..6f8a7b1110 --- /dev/null +++ b/plugins/explore-backend/src/service/router.test.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { + ExploreTool, + GetExploreToolsRequest, +} from '@backstage/plugin-explore-common'; +import express from 'express'; +import request from 'supertest'; + +import { createRouter } from './router'; +import { ExploreToolProvider } from './types'; + +const mockTools: ExploreTool[] = [ + { title: 'Tool 1', url: 'https://example.com/tool1', image: '' }, + { title: 'Tool 2', url: 'https://example.com/tool2', image: '' }, + { title: 'Tool 3', url: 'https://example.com/tool2', image: '' }, +]; + +describe('createRouter', () => { + let app: express.Express; + const toolProvider: ExploreToolProvider = { + getTools: async ({}: GetExploreToolsRequest) => { + return { + tools: mockTools, + }; + }, + }; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + toolProvider, + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /tools', () => { + it('returns ok', async () => { + const response = await request(app).get('/tools'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual( + expect.objectContaining({ tools: mockTools }), + ); + }); + }); +}); diff --git a/plugins/explore-backend/src/service/router.ts b/plugins/explore-backend/src/service/router.ts new file mode 100644 index 0000000000..22ffcb54c9 --- /dev/null +++ b/plugins/explore-backend/src/service/router.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { errorHandler } from '@backstage/backend-common'; +import { GetExploreToolsRequest } from '@backstage/plugin-explore-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; +import { ExploreToolProvider } from './types'; + +export interface RouterOptions { + logger: Logger; + toolProvider: ExploreToolProvider; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + const { toolProvider } = options; + + const router = Router(); + router.use(express.json()); + + router.get('/tools', async (request, response) => { + const requestQuery = parseExploreToolRequestQuery(request.query); + const result = await toolProvider.getTools(requestQuery); + response.json(result); + }); + + router.use(errorHandler()); + + return router; +} + +function parseExploreToolRequestQuery( + params: Record, +): GetExploreToolsRequest { + // TODO: Implement proper query parsing for the filters + // Also consider using a `filter` query string like catalog client does + return { + filter: { + tags: [...[(params?.tag as any) ?? []]].flat(), + lifecycle: [...[(params?.lifecycle as any) ?? []]].flat(), + }, + }; +} diff --git a/plugins/explore-backend/src/service/standaloneServer.ts b/plugins/explore-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..6cf5852f18 --- /dev/null +++ b/plugins/explore-backend/src/service/standaloneServer.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createServiceBuilder } from '@backstage/backend-common'; +import { GetExploreToolsRequest } from '@backstage/plugin-explore-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ + service: 'explore-backend', + }); + + logger.debug('Starting application server...'); + + const router = await createRouter({ + logger, + toolProvider: { + getTools: async ({}: GetExploreToolsRequest) => { + return { + filter: undefined, + tools: [ + { title: 'Tool 1', url: 'https://example.com/tool1', image: '' }, + { title: 'Tool 2', url: 'https://example.com/tool2', image: '' }, + { title: 'Tool 3', url: 'https://example.com/tool2', image: '' }, + ], + }; + }, + }, + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/explore-backend', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/explore-backend/src/service/types.ts b/plugins/explore-backend/src/service/types.ts new file mode 100644 index 0000000000..c2bccaa478 --- /dev/null +++ b/plugins/explore-backend/src/service/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + GetExploreToolsRequest, + GetExploreToolsResponse, +} from '@backstage/plugin-explore-common'; + +/** + * @public + */ +export interface ExploreToolProvider { + /** + * Gets a list of explore tools filtered by the request options. + * + * @param request The request options {@link GetExploreToolsRequest}. + * @returns The response {@link GetExploreToolsResponse}. + */ + getTools(request: GetExploreToolsRequest): Promise; +} diff --git a/plugins/explore-backend/src/setupTests.ts b/plugins/explore-backend/src/setupTests.ts new file mode 100644 index 0000000000..813cdeaae3 --- /dev/null +++ b/plugins/explore-backend/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/plugins/explore-common/.eslintrc.js b/plugins/explore-common/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/explore-common/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/explore-common/README.md b/plugins/explore-common/README.md new file mode 100644 index 0000000000..dfd99ae24a --- /dev/null +++ b/plugins/explore-common/README.md @@ -0,0 +1,3 @@ +# explore-common + +Shared types for the `explore*` plugins. diff --git a/plugins/explore-common/api-report.md b/plugins/explore-common/api-report.md new file mode 100644 index 0000000000..79cf80186c --- /dev/null +++ b/plugins/explore-common/api-report.md @@ -0,0 +1,37 @@ +## API Report File for "@backstage/plugin-explore-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// @public (undocumented) +export type ExploreTool = { + title: string; + description?: string; + url: string; + image: string; + tags?: string[]; + lifecycle?: string; +}; + +// @public (undocumented) +export type ExploreToolFilter = { + tags?: string[]; + lifecycle?: string[]; +}; + +// @public (undocumented) +export type GetExploreToolsRequest = { + filter?: ExploreToolFilter; +}; + +// @public (undocumented) +export type GetExploreToolsResponse = { + tools: ExploreTool[]; +}; + +// Warnings were encountered during analysis: +// +// src/index.d.ts:2:1 - (ae-misplaced-package-tag) The @packageDocumentation comment must appear at the top of entry point *.d.ts file + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/explore-common/package.json b/plugins/explore-common/package.json new file mode 100644 index 0000000000..6365eca8e5 --- /dev/null +++ b/plugins/explore-common/package.json @@ -0,0 +1,39 @@ +{ + "name": "@backstage/plugin-explore-common", + "description": "Common functionalities for the explore plugin", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "common-library" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/explore-common" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "devDependencies": { + "@backstage/cli": "workspace:^" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/explore-common/src/api.ts b/plugins/explore-common/src/api.ts new file mode 100644 index 0000000000..256671c1af --- /dev/null +++ b/plugins/explore-common/src/api.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExploreTool } from './types'; + +/** + * @public + */ +export type ExploreToolFilter = { + /** + * Filter by a list of tags + */ + tags?: string[]; + + /** + * Filter by a list of lifecycle strings + */ + lifecycle?: string[]; +}; + +/** + * @public + */ +export type GetExploreToolsRequest = { + filter?: ExploreToolFilter; +}; + +/** + * @public + */ +export type GetExploreToolsResponse = { + tools: ExploreTool[]; +}; diff --git a/plugins/explore-common/src/index.ts b/plugins/explore-common/src/index.ts new file mode 100644 index 0000000000..b5721d9f57 --- /dev/null +++ b/plugins/explore-common/src/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/***/ +/** + * Common functionalities for the explore plugin. + * + * @packageDocumentation + */ + +export * from './api'; +export * from './types'; diff --git a/plugins/explore-common/src/types.ts b/plugins/explore-common/src/types.ts new file mode 100644 index 0000000000..25f995f807 --- /dev/null +++ b/plugins/explore-common/src/types.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @public + */ +export type ExploreTool = { + title: string; + description?: string; + url: string; + image: string; + tags?: string[]; + lifecycle?: string; +}; diff --git a/plugins/explore-react/api-report.md b/plugins/explore-react/api-report.md index 8a9d2b8b13..61b3cbee2d 100644 --- a/plugins/explore-react/api-report.md +++ b/plugins/explore-react/api-report.md @@ -4,23 +4,17 @@ ```ts import { ApiRef } from '@backstage/core-plugin-api'; +import * as common from '@backstage/plugin-explore-common'; -// @public (undocumented) -export type ExploreTool = { - title: string; - description?: string; - url: string; - image: string; - tags?: string[]; - lifecycle?: string; -}; +// @public @deprecated (undocumented) +export type ExploreTool = common.ExploreTool; -// @public (undocumented) +// @public @deprecated (undocumented) export interface ExploreToolsConfig { // (undocumented) getTools: () => Promise; } -// @public (undocumented) +// @public @deprecated (undocumented) export const exploreToolsConfigRef: ApiRef; ``` diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 879a00daa9..c20679de78 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -32,7 +32,8 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/core-plugin-api": "workspace:^" + "@backstage/core-plugin-api": "workspace:^", + "@backstage/plugin-explore-common": "workspace:^" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/explore-react/src/tools/api.ts b/plugins/explore-react/src/tools/api.ts index 0fae2ce0fb..32dccfb5dd 100644 --- a/plugins/explore-react/src/tools/api.ts +++ b/plugins/explore-react/src/tools/api.ts @@ -16,22 +16,29 @@ import { createApiRef } from '@backstage/core-plugin-api'; -/** @public */ +/** + * Deprecated types moved to `@backstage/plugin-explore-common` + */ +import * as common from '@backstage/plugin-explore-common'; + +/** + * @deprecated Use ExploreTool from `@backstage/plugin-explore-common` + * @public + */ +export type ExploreTool = common.ExploreTool; + +/** + * @deprecated Use exploreApiRef from `@backstage/plugin-explore` & the `@backstage/plugin-explore-backend` + * @public + */ export const exploreToolsConfigRef = createApiRef({ id: 'plugin.explore.toolsconfig', }); -/** @public */ -export type ExploreTool = { - title: string; - description?: string; - url: string; - image: string; - tags?: string[]; - lifecycle?: string; -}; - -/** @public */ +/** + * @deprecated Use ExploreApi from `@backstage/plugin-explore` & the `@backstage/plugin-explore-backend` + * @public + */ export interface ExploreToolsConfig { getTools: () => Promise; } diff --git a/plugins/explore/package.json b/plugins/explore/package.json index f8388df6e8..cbd8d37932 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -35,8 +35,12 @@ "@backstage/catalog-model": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/plugin-explore-common": "workspace:^", "@backstage/plugin-explore-react": "workspace:^", + "@backstage/plugin-search-common": "workspace:^", + "@backstage/plugin-search-react": "workspace:^", "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/explore/src/api/client.ts b/plugins/explore/src/api/client.ts new file mode 100644 index 0000000000..56f8dd4167 --- /dev/null +++ b/plugins/explore/src/api/client.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; +import { ResponseError } from '@backstage/errors'; +import { + GetExploreToolsRequest, + GetExploreToolsResponse, +} from '@backstage/plugin-explore-common'; +import { ExploreApi } from './types'; + +/** + * Default implementation of the ExploreApi. + * + * @public + */ +export class ExploreClient implements ExploreApi { + private readonly discoveryApi: DiscoveryApi; + private readonly fetchApi: FetchApi; + + constructor({ + discoveryApi, + fetchApi, + }: { + discoveryApi: DiscoveryApi; + fetchApi: FetchApi; + }) { + this.discoveryApi = discoveryApi; + this.fetchApi = fetchApi; + } + + async getTools( + request: GetExploreToolsRequest = {}, + ): Promise { + const { fetch } = this.fetchApi; + + const filter = request.filter ?? {}; + const baseUrl = await this.discoveryApi.getBaseUrl('explore'); + + const tags = filter?.tags?.map(t => `tag=${encodeURIComponent(t)}`) ?? []; + const lifecycles = + filter?.lifecycle?.map(l => `lifecycle=${encodeURIComponent(l)}`) ?? []; + const query = [...tags, ...lifecycles].join('&'); + + const response = await fetch(`${baseUrl}/tools?${query}`); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + return response.json() as Promise; + } +} diff --git a/plugins/explore/src/api/index.ts b/plugins/explore/src/api/index.ts new file mode 100644 index 0000000000..a7ac3aa8c1 --- /dev/null +++ b/plugins/explore/src/api/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ExploreClient } from './client'; +export { exploreApiRef } from './types'; +export type { ExploreApi } from './types'; diff --git a/plugins/explore/src/api/types.ts b/plugins/explore/src/api/types.ts new file mode 100644 index 0000000000..0d9eb4ac07 --- /dev/null +++ b/plugins/explore/src/api/types.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef } from '@backstage/core-plugin-api'; +import { + GetExploreToolsRequest, + GetExploreToolsResponse, +} from '@backstage/plugin-explore-common'; + +/** + * Interface for the explore plugin. + * + * @public + */ +export interface ExploreApi { + /** + * Returns a list of explore tools. + * + * @param request - The The request query options + */ + getTools(request?: GetExploreToolsRequest): Promise; +} + +export const exploreApiRef = createApiRef({ + id: 'plugin.explore.service', +}); diff --git a/plugins/explore/src/components/ToolSearchResultListItem/ToolSearchResultListItem.tsx b/plugins/explore/src/components/ToolSearchResultListItem/ToolSearchResultListItem.tsx new file mode 100644 index 0000000000..9084f91507 --- /dev/null +++ b/plugins/explore/src/components/ToolSearchResultListItem/ToolSearchResultListItem.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { ReactNode } from 'react'; +import { + Box, + Chip, + Divider, + ListItem, + ListItemIcon, + ListItemText, + makeStyles, +} from '@material-ui/core'; +import { Link } from '@backstage/core-components'; +import { useAnalytics } from '@backstage/core-plugin-api'; +import { + IndexableDocument, + ResultHighlight, +} from '@backstage/plugin-search-common'; +import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; + +const useStyles = makeStyles({ + flexContainer: { + flexWrap: 'wrap', + }, + itemText: { + width: '100%', + wordBreak: 'break-all', + marginBottom: '1rem', + }, +}); + +/** + * Props for {@link ToolSearchResultListItem}. + * + * @public + */ +export interface ToolSearchResultListItemProps { + icon?: ReactNode; + result: IndexableDocument; + highlight?: ResultHighlight; + rank?: number; +} + +/** @public */ +export function ToolSearchResultListItem(props: ToolSearchResultListItemProps) { + const result = props.result as any; + + const classes = useStyles(); + const analytics = useAnalytics(); + const handleClick = () => { + analytics.captureEvent('discover', result.title, { + attributes: { to: result.location }, + value: props.rank, + }); + }; + + return ( + + + {props.icon && {props.icon}} +
+ + ) : ( + result.title + ) + } + secondary={ + props.highlight?.fields.text ? ( + + ) : ( + result.text + ) + } + /> + + {result.tags && + result.tags.map((tag: string) => ( + + ))} + +
+
+ + + ); +} diff --git a/plugins/explore/src/components/ToolSearchResultListItem/index.ts b/plugins/explore/src/components/ToolSearchResultListItem/index.ts new file mode 100644 index 0000000000..1dfdd64b5b --- /dev/null +++ b/plugins/explore/src/components/ToolSearchResultListItem/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './ToolSearchResultListItem'; diff --git a/plugins/explore/src/components/index.ts b/plugins/explore/src/components/index.ts index f8fba16910..d3af1b99c2 100644 --- a/plugins/explore/src/components/index.ts +++ b/plugins/explore/src/components/index.ts @@ -16,3 +16,4 @@ export * from './DomainCard'; export * from './ExploreLayout'; +export * from './ToolSearchResultListItem'; diff --git a/plugins/explore/src/index.ts b/plugins/explore/src/index.ts index 04f0240968..24591211f7 100644 --- a/plugins/explore/src/index.ts +++ b/plugins/explore/src/index.ts @@ -20,6 +20,7 @@ * @packageDocumentation */ +export * from './api'; export * from './components'; export * from './extensions'; export { explorePlugin, explorePlugin as plugin } from './plugin'; diff --git a/plugins/explore/src/plugin.ts b/plugins/explore/src/plugin.ts index a4bb538265..7ea9733054 100644 --- a/plugins/explore/src/plugin.ts +++ b/plugins/explore/src/plugin.ts @@ -16,21 +16,43 @@ import { exploreToolsConfigRef } from '@backstage/plugin-explore-react'; import { catalogEntityRouteRef, exploreRouteRef } from './routes'; -import { exampleTools } from './util/examples'; -import { createApiFactory, createPlugin } from '@backstage/core-plugin-api'; +import { + createApiFactory, + createPlugin, + discoveryApiRef, + fetchApiRef, +} from '@backstage/core-plugin-api'; +import { ExploreClient, exploreApiRef } from './api'; /** @public */ export const explorePlugin = createPlugin({ id: 'explore', apis: [ - // Register a default for exploreToolsConfigRef, you may want to override - // the API locally in your app. + createApiFactory({ + api: exploreApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ discoveryApi, fetchApi }) => + new ExploreClient({ discoveryApi, fetchApi }), + }), + /** + * @deprecated Use ExploreApi from `@backstage/plugin-explore` instead + * + * Register a default for exploreToolsConfigRef, you may want to override + * the API locally in your app. + */ createApiFactory({ api: exploreToolsConfigRef, - deps: {}, - factory: () => ({ + deps: { + exploreApi: exploreApiRef, + }, + factory: ({ exploreApi }) => ({ async getTools() { - return exampleTools; + // TODO: Can we make this backwards compatible so it works off the static frontend example tools? + // return exampleTools; + return (await exploreApi.getTools()).tools; }, }), }), diff --git a/yarn.lock b/yarn.lock index acb2e147e2..fe3feb9581 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5988,6 +5988,36 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-explore-backend@workspace:^, @backstage/plugin-explore-backend@workspace:plugins/explore-backend": + version: 0.0.0-use.local + resolution: "@backstage/plugin-explore-backend@workspace:plugins/explore-backend" + dependencies: + "@backstage/backend-common": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/plugin-explore-common": "workspace:^" + "@backstage/plugin-search-common": "workspace:^" + "@types/express": "*" + "@types/supertest": ^2.0.8 + express: ^4.18.1 + express-promise-router: ^4.1.0 + lodash: ^4.17.21 + msw: ^0.47.0 + node-fetch: ^2.6.7 + supertest: ^6.2.4 + winston: ^3.2.1 + yn: ^4.0.0 + languageName: unknown + linkType: soft + +"@backstage/plugin-explore-common@workspace:^, @backstage/plugin-explore-common@workspace:plugins/explore-common": + version: 0.0.0-use.local + resolution: "@backstage/plugin-explore-common@workspace:plugins/explore-common" + dependencies: + "@backstage/cli": "workspace:^" + languageName: unknown + linkType: soft + "@backstage/plugin-explore-react@workspace:^, @backstage/plugin-explore-react@workspace:plugins/explore-react": version: 0.0.0-use.local resolution: "@backstage/plugin-explore-react@workspace:plugins/explore-react" @@ -5995,6 +6025,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/plugin-explore-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@testing-library/jest-dom": ^5.10.1 "@testing-library/react": ^12.1.3 @@ -6015,9 +6046,13 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/plugin-explore-common": "workspace:^" "@backstage/plugin-explore-react": "workspace:^" + "@backstage/plugin-search-common": "workspace:^" + "@backstage/plugin-search-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@material-ui/core": ^4.12.2 @@ -21866,6 +21901,8 @@ __metadata: "@backstage/plugin-code-coverage-backend": "workspace:^" "@backstage/plugin-events-backend": "workspace:^" "@backstage/plugin-events-node": "workspace:^" + "@backstage/plugin-explore-backend": "workspace:^" + "@backstage/plugin-explore-common": "workspace:^" "@backstage/plugin-graphql-backend": "workspace:^" "@backstage/plugin-jenkins-backend": "workspace:^" "@backstage/plugin-kafka-backend": "workspace:^" @@ -29054,6 +29091,41 @@ __metadata: languageName: node linkType: hard +"msw@npm:^0.47.0": + version: 0.47.4 + resolution: "msw@npm:0.47.4" + dependencies: + "@mswjs/cookies": ^0.2.2 + "@mswjs/interceptors": ^0.17.5 + "@open-draft/until": ^1.0.3 + "@types/cookie": ^0.4.1 + "@types/js-levenshtein": ^1.1.1 + chalk: 4.1.1 + chokidar: ^3.4.2 + cookie: ^0.4.2 + graphql: ^15.0.0 || ^16.0.0 + headers-polyfill: ^3.1.0 + inquirer: ^8.2.0 + is-node-process: ^1.0.1 + js-levenshtein: ^1.1.6 + node-fetch: ^2.6.7 + outvariant: ^1.3.0 + path-to-regexp: ^6.2.0 + statuses: ^2.0.0 + strict-event-emitter: ^0.2.6 + type-fest: ^2.19.0 + yargs: ^17.3.1 + peerDependencies: + typescript: ">= 4.2.x <= 4.8.x" + peerDependenciesMeta: + typescript: + optional: true + bin: + msw: cli/index.js + checksum: 10ff632641d40384d6622abf4df6399e4ae649db0f676b5d1ee2d0a515ec96f33abe9d4fecba08cdba4b2e43255af419da9eefc020d40a7e10669d0906457197 + languageName: node + linkType: hard + "msw@npm:^0.49.0": version: 0.49.1 resolution: "msw@npm:0.49.1"