diff --git a/README.md b/README.md index c1ca77c2cc..2f3740c36d 100644 --- a/README.md +++ b/README.md @@ -10,29 +10,39 @@ ## What is Backstage? -Backstage is an open platform for building developer portals. - -The philosophy behind Backstage is simple: Don't expose your engineers to the full complexity of your infrastructure tooling. Engineers should be shipping code — not figuring out a whole new toolset every time they want to implement the basics. Backstage allows you add "stuff" (tooling, services, features, etc.) by adding a plugin, instead of building a new tool. This saves you work and avoids the need of your team to learn how to use and support yet another tool. +[Backstage](https://backstage.io/) is an open platform for building developer portals. It’s based on the developer portal we’ve been using internally at Spotify for over four years. Backstage can be as simple as a services catalog or as powerful as the UX layer for your entire tech infrastructure. For more information go to [backstage.io](https://backstage.io) or join our [Discord chatroom](https://discord.gg/EBHEGzX). -## What problem does Backstage solve? +### Features +* Create and manage all of your organization’s software and microservices in one place +* Services catalog keeps track of all software and its ownership +* Visualizations provide information about your backend services and tooling, and help you monitor them +* A unified method for managing microservices offers both visibility and control +* Preset templates allow engineers to quickly create microservices in a standardized way ([coming soon](https://github.com/spotify/backstage/milestone/11)) +* Centralized, full-featured technical documentation with integrated tooling that makes it easy for developers to set up, publish, and maintain alongside their code ([coming soon](https://github.com/spotify/backstage/milestone/15)) -As companies grow, their infrastructure systems get messier. Backstage unifies all your infrastructure tooling, services, and documentation with a single, consistent UI. +### Benefits +* For engineering managers, it allows you to maintain standards and best practices across the organization, and can help you manage your whole tech ecosystem, from migrations to test certification. +* For end users (developers), it makes it fast and simple to build software components in a standardized way, and it provides a central place to manage all projects and documentation. +* For platform engineers, it enables extensibility and scalability by letting you easily integrate new tools and services (via plugins), as well as extending the functionality of existing ones. +* For everyone, it’s a single, consistent experience that ties all your infrastructure tooling, resources, standards, owners, contributors, and administrators together in one place. -This blog post provides more examples of how Backstage is used inside Spotify: +## Backstage Service Catalog (alpha) -https://labs.spotify.com/2020/03/17/what-the-heck-is-backstage-anyway/ +The Backstage Service Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata yaml files](https://github.com/spotify/backstage/blob/master/docs/architecture-decisions/adr002-default-catalog-file-format.md#format) stored together with the code, which are then harvested and visualized in Backstage. -https://backstage.io/demos +![servce-catalog](https://backstage.io/blog/assets/6/header.png) + +We have also found that the service catalog is a great way to organise the infrastructure tools you use to manage the software as well. This is how Backstage creates one developer portal for all your tools. Rather than asking teams to jump between different infrastructure UI’s (and incurring additional cognitive overhead each time they make a context switch), most of these tools can be organised around the entities in the catalog. ## Project roadmap We created Backstage about 4 years ago. While our internal version of Backstage has had the benefit of time to mature and evolve, the first iteration of our open source version is still nascent. We are envisioning three phases of the project and we have already begun work on various aspects of these phases: -- 🐣 **Phase 1:** Extensible frontend platform (Done ✅) - You will be able to easily create a single consistent UI layer for your internal infrastructure and tools. A set of reusable UX patterns and components help ensure a consistent experience between tools. +- 🐣 **Phase 1:** Extensible frontend platform (Done ✅) - You will be able to easily create a single consistent UI layer for your internal infrastructure and tools. A set of reusable [UX patterns and components](http://storybook.backstage.io) help ensure a consistent experience between tools. -- 🐢 **Phase 2:** Manage your stuff ([current focus](https://backstage.io/blog/2020/05/22/phase-2-service-catalog)) - Manage anything from microservices to software components to infrastructure and your service catalog. Regardless of whether you want to create a new library, view service deployment status in Kubernetes, or check the test coverage for a website -- Backstage will provide all of those tools - and many more - in a single developer portal. +- 🐢 **Phase 2:** Service Catalog ([alpha released](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)) - With a single catalog, Backstage makes it easy for a team to manage ten services — and makes it possible for your company to manage thousands of them. Developers can get a uniform overview of all their software and related resources, regardless of how and where they are running, as well as an easy way to onboard and manage those resources. - 🐇 **Phase 3:** Ecosystem (later) - Everyone's infrastructure stack is different. By fostering a vibrant community of contributors we hope to provide an ecosystem of Open Source plugins/integrations that allows you to pick the tools that match your stack. @@ -46,14 +56,12 @@ The Backstage platform consists of a number of different components: - **app** - Main web application that users interact with. It's built up by a number of different _Plugins_. This repo contains an example implementation of an app (located in `packages/example-app`) and you can easily get started with your own app by [creating one](docs/create-an-app.md). - [**plugins**](https://github.com/spotify/backstage/tree/master/plugins) - Each plugin is treated as a self-contained web app and can include almost any type of content. Plugins all use a common set of platform API's and reusable UI components. Plugins can fetch data either from the _backend_ or through any RESTful API exposed through the _proxy_. -- [**backend**](https://github.com/spotify/backstage/tree/master/packages/backend) - GraphQL aggregation service that holds the model of your software ecosystem, including organisational information and what team owns what software. The backend also has a Plugin model for extending its graph. +- [**service catalog**](https://github.com/spotify/backstage/tree/master/packages/backend) - Service that holds the model of your software ecosystem, including organisational information and what team owns what software. The backend also has a Plugin model for extending its graph. - **proxy** \* - Terminates HTTPS and exposes any RESTful API to Plugins. -- **identity** \* - A backend service that holds your organisation's metadata. +- **identity** - A backend service that holds your organisation's metadata. _\* not yet released_ -![overview](backstage_overview.png) - ## Getting started To run a Backstage app, you will need to have the following installed: @@ -100,6 +108,7 @@ We would love your help in building Backstage! See [CONTRIBUTING](CONTRIBUTING.m - [FAQ](docs/FAQ.md) - Frequently Asked Questions - [Code of Conduct](CODE_OF_CONDUCT.md) - This is how we roll - [Blog](https://backstage.io/blog/) - Announcements and updates +- [Newsletter](https://mailchi.mp/spotify/backstage-community) - Give us a star ⭐️ - If you are using Backstage or think it is an interesting project, we would love a star ❤️ Or, if you are an open source developer and are interested in joining our team, please reach out to [foss-opportunities@spotify.com ](mailto:foss-opportunities@spotify.com) diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts index eee16ea0de..0fe87440fb 100644 --- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts +++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts @@ -26,6 +26,7 @@ import { AnnotateLocationEntityProcessor } from './processors/AnnotateLocationEn import { EntityPolicyProcessor } from './processors/EntityPolicyProcessor'; import { FileReaderProcessor } from './processors/FileReaderProcessor'; import { GithubReaderProcessor } from './processors/GithubReaderProcessor'; +import { GitlabReaderProcessor } from './processors/GitlabReaderProcessor'; import { LocationRefProcessor } from './processors/LocationEntityProcessor'; import * as result from './processors/results'; import { @@ -56,6 +57,7 @@ export class LocationReaders implements LocationReader { return [ new FileReaderProcessor(), new GithubReaderProcessor(), + new GitlabReaderProcessor(), new YamlProcessor(), new EntityPolicyProcessor(entityPolicy), new LocationRefProcessor(), diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts new file mode 100644 index 0000000000..211f325afe --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { LocationSpec } from '@backstage/catalog-model'; +import fetch from 'node-fetch'; +import * as result from './results'; +import { LocationProcessor, LocationProcessorEmit } from './types'; + +export class GitlabReaderProcessor implements LocationProcessor { + async readLocation( + location: LocationSpec, + optional: boolean, + emit: LocationProcessorEmit, + ): Promise { + if (location.type !== 'gitlab') { + return false; + } + + try { + const url = this.buildRawUrl(location.target); + + const response = await fetch(url.toString()); + + if (response.ok) { + const data = await response.buffer(); + emit(result.data(location, data)); + } else { + const message = `${location.target} could not be read as ${url}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + if (!optional) { + throw result.notFoundError(location, message); + } + } else { + throw result.generalError(location, message); + } + } + } catch (e) { + const message = `Unable to read ${location.type} ${location.target}, ${e}`; + emit(result.generalError(location, message)); + } + + return true; + } + + // Converts + // from: https://gitlab.example.com/a/b/blob/master/c.yaml + // to: https://gitlab.example.com/a/b/raw/master/c.yaml + private buildRawUrl(target: string): URL { + try { + const url = new URL(target); + + const [ + empty, + userOrOrg, + repoName, + blobKeyword, + ...restOfPath + ] = url.pathname.split('/'); + + if ( + empty !== '' || + userOrOrg === '' || + repoName === '' || + blobKeyword !== 'blob' || + !restOfPath.join('/').match(/\.yaml$/) + ) { + throw new Error('Wrong Gitlab URL'); + } + + // Replace 'blob' with 'raw' + url.pathname = [empty, userOrOrg, repoName, 'raw', ...restOfPath].join( + '/', + ); + + return url; + } catch (e) { + throw new Error(`Incorrect url: ${target}, ${e}`); + } + } +} diff --git a/plugins/techdocs/mkdocs/container/techdocs-core/src/core.py b/plugins/techdocs/mkdocs/container/techdocs-core/src/core.py index 18c22c40e3..e6c4a0403d 100644 --- a/plugins/techdocs/mkdocs/container/techdocs-core/src/core.py +++ b/plugins/techdocs/mkdocs/container/techdocs-core/src/core.py @@ -44,42 +44,42 @@ class TechDocsCore(BasePlugin): config["plugins"]["search"] = search_plugin # Markdown Extensions - config['markdown_extensions'].append('admonition') - config['markdown_extensions'].append('abbr') - config['markdown_extensions'].append('attr_list') - config['markdown_extensions'].append('def_list') - config['markdown_extensions'].append('codehilite') - config['mdx_configs']['codehilite'] = { - 'linenums': True, - 'guess_lang': False, - 'pygments_style': 'friendly', + config["markdown_extensions"].append("admonition") + config["markdown_extensions"].append("abbr") + config["markdown_extensions"].append("attr_list") + config["markdown_extensions"].append("def_list") + config["markdown_extensions"].append("codehilite") + config["mdx_configs"]["codehilite"] = { + "linenums": True, + "guess_lang": False, + "pygments_style": "friendly", } - config['markdown_extensions'].append('toc') - config['mdx_configs']['toc'] = { - 'permalink': True, + config["markdown_extensions"].append("toc") + config["mdx_configs"]["toc"] = { + "permalink": True, } - config['markdown_extensions'].append('footnotes') - config['markdown_extensions'].append('markdown.extensions.tables') - config['markdown_extensions'].append('pymdownx.betterem') - config['mdx_configs']['pymdownx.betterem'] = { - 'smart_enable': 'all', + config["markdown_extensions"].append("footnotes") + config["markdown_extensions"].append("markdown.extensions.tables") + config["markdown_extensions"].append("pymdownx.betterem") + config["mdx_configs"]["pymdownx.betterem"] = { + "smart_enable": "all", } - config['markdown_extensions'].append('pymdownx.caret') - config['markdown_extensions'].append('pymdownx.critic') - config['markdown_extensions'].append('pymdownx.details') - config['markdown_extensions'].append('pymdownx.emoji') - config['mdx_configs']['pymdownx.emoji'] = { - 'emoji_generator': '!!python/name:pymdownx.emoji.to_svg', + config["markdown_extensions"].append("pymdownx.caret") + config["markdown_extensions"].append("pymdownx.critic") + config["markdown_extensions"].append("pymdownx.details") + config["markdown_extensions"].append("pymdownx.emoji") + config["mdx_configs"]["pymdownx.emoji"] = { + "emoji_generator": "!!python/name:pymdownx.emoji.to_svg", } - config['markdown_extensions'].append('pymdownx.inlinehilite') - config['markdown_extensions'].append('pymdownx.magiclink') - config['markdown_extensions'].append('pymdownx.mark') - config['markdown_extensions'].append('pymdownx.smartsymbols') - config['markdown_extensions'].append('pymdownx.superfences') - config['markdown_extensions'].append('pymdownx.tasklist') - config['mdx_configs']['pymdownx.tasklist'] = { - 'custom_checkbox': True, + config["markdown_extensions"].append("pymdownx.inlinehilite") + config["markdown_extensions"].append("pymdownx.magiclink") + config["markdown_extensions"].append("pymdownx.mark") + config["markdown_extensions"].append("pymdownx.smartsymbols") + config["markdown_extensions"].append("pymdownx.superfences") + config["markdown_extensions"].append("pymdownx.tasklist") + config["mdx_configs"]["pymdownx.tasklist"] = { + "custom_checkbox": True, } - config['markdown_extensions'].append('pymdownx.tilde') + config["markdown_extensions"].append("pymdownx.tilde") return config diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 1f83d6290f..9ef290fa40 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@backstage/core": "^0.1.1-alpha.12", + "@backstage/test-utils": "^0.1.1-alpha.12", "@backstage/theme": "^0.1.1-alpha.12", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", diff --git a/plugins/techdocs/src/components/ExampleComponent/ExampleComponent.test.tsx b/plugins/techdocs/src/components/ExampleComponent/ExampleComponent.test.tsx deleted file mode 100644 index e4d760526e..0000000000 --- a/plugins/techdocs/src/components/ExampleComponent/ExampleComponent.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 from 'react'; -import { render } from '@testing-library/react'; -import mockFetch from 'jest-fetch-mock'; -import ExampleComponent from './ExampleComponent'; -import { ThemeProvider } from '@material-ui/core'; -import { lightTheme } from '@backstage/theme'; - -describe('ExampleComponent', () => { - it('should render', () => { - mockFetch.mockResponse(() => new Promise(() => {})); - const rendered = render( - - - , - ); - expect(rendered.getByText('Welcome to techdocs!')).toBeInTheDocument(); - }); -}); diff --git a/plugins/techdocs/src/components/ExampleComponent/ExampleComponent.tsx b/plugins/techdocs/src/components/ExampleComponent/ExampleComponent.tsx deleted file mode 100644 index 2ab29604ee..0000000000 --- a/plugins/techdocs/src/components/ExampleComponent/ExampleComponent.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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, { FC } from 'react'; -import { Typography, Grid } from '@material-ui/core'; -import { - InfoCard, - Header, - Page, - pageTheme, - Content, - ContentHeader, - HeaderLabel, - SupportButton, -} from '@backstage/core'; -import ExampleFetchComponent from '../ExampleFetchComponent'; - -const ExampleComponent: FC<{}> = () => ( - -
- - -
- - - A description of your plugin goes here. - - - - - - All content should be wrapped in a card like this. - - - - - - - - -
-); - -export default ExampleComponent; diff --git a/plugins/techdocs/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx b/plugins/techdocs/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx deleted file mode 100644 index c2139befec..0000000000 --- a/plugins/techdocs/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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, { FC } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { Table, TableColumn, Progress } from '@backstage/core'; -import Alert from '@material-ui/lab/Alert'; -import { useAsync } from 'react-use'; - -const useStyles = makeStyles({ - avatar: { - height: 32, - width: 32, - borderRadius: '50%', - }, -}); - -type User = { - gender: string; // "male" - name: { - title: string; // "Mr", - first: string; // "Duane", - last: string; // "Reed" - }; - location: object; // {street: {number: 5060, name: "Hickory Creek Dr"}, city: "Albany", state: "New South Wales",…} - email: string; // "duane.reed@example.com" - login: object; // {uuid: "4b785022-9a23-4ab9-8a23-cb3fb43969a9", username: "blackdog796", password: "patch",…} - dob: object; // {date: "1983-06-22T12:30:23.016Z", age: 37} - registered: object; // {date: "2006-06-13T18:48:28.037Z", age: 14} - phone: string; // "07-2154-5651" - cell: string; // "0405-592-879" - id: { - name: string; // "TFN", - value: string; // "796260432" - }; - picture: { medium: string }; // {medium: "https://randomuser.me/api/portraits/men/95.jpg",…} - nat: string; // "AU" -}; - -type DenseTableProps = { - users: User[]; -}; - -export const DenseTable: FC = ({ users }) => { - const classes = useStyles(); - - const columns: TableColumn[] = [ - { title: 'Avatar', field: 'avatar' }, - { title: 'Name', field: 'name' }, - { title: 'Email', field: 'email' }, - { title: 'Nationality', field: 'nationality' }, - ]; - - const data = users.map(user => { - return { - avatar: ( - {user.name.first} - ), - name: `${user.name.first} ${user.name.last}`, - email: user.email, - nationality: user.nat, - }; - }); - - return ( - - ); -}; - -const ExampleFetchComponent: FC<{}> = () => { - const { value, loading, error } = useAsync(async (): Promise => { - const response = await fetch('https://randomuser.me/api/?results=20'); - const data = await response.json(); - return data.results; - }, []); - - if (loading) { - return ; - } else if (error) { - return {error.message}; - } - - return ; -}; - -export default ExampleFetchComponent; diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index 29f9f8b085..23bfa3b714 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -30,16 +30,16 @@ */ import { createPlugin, createRouteRef } from '@backstage/core'; -import ExampleComponent from './components/ExampleComponent'; +import { Reader } from './reader/components/Reader'; export const rootRouteRef = createRouteRef({ - path: '/techdocs', - title: 'techdocs', + path: '/docs', + title: 'Docs', }); export const plugin = createPlugin({ id: 'techdocs', register({ router }) { - router.addRoute(rootRouteRef, ExampleComponent); + router.addRoute(rootRouteRef, Reader); }, }); diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx new file mode 100644 index 0000000000..e306a72424 --- /dev/null +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -0,0 +1,816 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 from 'react'; +import { useShadowDom } from '..'; + +const mockHtml: string = ` + + + + + + + + + + + + + + + + + + + + + + + + + + Download boilerplate - MkDocs Material Boilerplate + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ + + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + + + + + + + + +

Download boilerplate

+

Git clone

+
git clone https://github.com/peaceiris/mkdocs-material-boilerplate.git
+cd mkdocs-material-boilerplate
+
+ + +

Download zip

+
wget 'https://github.com/peaceiris/mkdocs-material-boilerplate/archive/master.zip'
+unzip master.zip
+cd mkdocs-material-boilerplate-master
+
+ + +

👉 Click me to download zip

+ + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + +`; + +export const Reader = () => { + const shadowDomRef = useShadowDom(); + + React.useEffect(() => { + const divElement = shadowDomRef.current; + if (!divElement?.shadowRoot) { + return; + } + divElement.shadowRoot.innerHTML = mockHtml; + }, [shadowDomRef]); + + return ( + <> +

Shadow DOM should be underneath

+
+ + ); +}; diff --git a/plugins/techdocs/src/components/ExampleComponent/index.ts b/plugins/techdocs/src/reader/hooks/index.ts similarity index 92% rename from plugins/techdocs/src/components/ExampleComponent/index.ts rename to plugins/techdocs/src/reader/hooks/index.ts index e785d45082..f66c5303ed 100644 --- a/plugins/techdocs/src/components/ExampleComponent/index.ts +++ b/plugins/techdocs/src/reader/hooks/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { default } from './ExampleComponent'; +export { useShadowDom } from './shadowDom'; diff --git a/plugins/techdocs/src/reader/hooks/shadowDom.test.tsx b/plugins/techdocs/src/reader/hooks/shadowDom.test.tsx new file mode 100644 index 0000000000..1d58bf26e7 --- /dev/null +++ b/plugins/techdocs/src/reader/hooks/shadowDom.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 from 'react'; +import { renderWithEffects } from '@backstage/test-utils'; +import { useShadowDom } from './shadowDom'; + +const ComponentWithoutHook = () => { + return
; +}; + +const ComponentWithHook = () => { + const ref = useShadowDom(); + return
; +}; + +describe('useShadowDom', () => { + it('does not create a Shadow DOM instance', async () => { + const rendered = await renderWithEffects(); + + const divElement = rendered.getByTestId('shadow-dom'); + expect(divElement.shadowRoot).not.toBeInstanceOf(ShadowRoot); + }); + + it('create a Shadow DOM instance', async () => { + const rendered = await renderWithEffects(); + + const divElement = rendered.getByTestId('shadow-dom'); + expect(divElement.shadowRoot).toBeInstanceOf(ShadowRoot); + }); +}); diff --git a/plugins/techdocs/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx b/plugins/techdocs/src/reader/hooks/shadowDom.ts similarity index 56% rename from plugins/techdocs/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx rename to plugins/techdocs/src/reader/hooks/shadowDom.ts index 7fecdc6f11..5edb8f4e4d 100644 --- a/plugins/techdocs/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx +++ b/plugins/techdocs/src/reader/hooks/shadowDom.ts @@ -14,15 +14,17 @@ * limitations under the License. */ -import React from 'react'; -import { render } from '@testing-library/react'; -import mockFetch from 'jest-fetch-mock'; -import ExampleFetchComponent from './ExampleFetchComponent'; +import { useEffect, useRef } from 'react'; +import type { RefObject } from 'react'; -describe('ExampleFetchComponent', () => { - it('should render', async () => { - mockFetch.mockResponse(() => new Promise(() => {})); - const rendered = render(); - expect(await rendered.findByTestId('progress')).toBeInTheDocument(); - }); -}); +type IShadowDOMRefObject = RefObject; +export const useShadowDom: () => IShadowDOMRefObject = () => { + const ref: IShadowDOMRefObject = useRef(null); + + useEffect(() => { + const divElement = ref.current; + divElement?.attachShadow({ mode: 'open' }); + }, [ref]); + + return ref; +}; diff --git a/plugins/techdocs/src/components/ExampleFetchComponent/index.ts b/plugins/techdocs/src/reader/index.tsx similarity index 92% rename from plugins/techdocs/src/components/ExampleFetchComponent/index.ts rename to plugins/techdocs/src/reader/index.tsx index 28482f9fe1..72b51bd0dc 100644 --- a/plugins/techdocs/src/components/ExampleFetchComponent/index.ts +++ b/plugins/techdocs/src/reader/index.tsx @@ -14,4 +14,4 @@ * limitations under the License. */ -export { default } from './ExampleFetchComponent'; +export * from './hooks';