diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx
index 52dd397418..29211dc42a 100644
--- a/packages/app/src/components/Root/Root.tsx
+++ b/packages/app/src/components/Root/Root.tsx
@@ -20,6 +20,7 @@ import HomeIcon from '@material-ui/icons/Home';
import ExtensionIcon from '@material-ui/icons/Extension';
import RuleIcon from '@material-ui/icons/AssignmentTurnedIn';
import MapIcon from '@material-ui/icons/MyLocation';
+import LayersIcon from '@material-ui/icons/Layers';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import MoneyIcon from '@material-ui/icons/MonetizationOn';
@@ -86,6 +87,7 @@ const Root = ({ children }: PropsWithChildren<{}>) => (
{/* End global nav */}
+
+```
diff --git a/plugins/ecosystem/dev/index.tsx b/plugins/ecosystem/dev/index.tsx
new file mode 100644
index 0000000000..30a071862b
--- /dev/null
+++ b/plugins/ecosystem/dev/index.tsx
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 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 { Entity } from '@backstage/catalog-model';
+import { createDevApp } from '@backstage/dev-utils';
+import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
+import { plugin } from '../src/plugin';
+
+createDevApp()
+ .registerPlugin(plugin)
+ .registerApi({
+ api: catalogApiRef,
+ deps: {},
+ factory: () =>
+ ({
+ async getEntities() {
+ const domainNames = [
+ 'playback',
+ 'artists',
+ 'payments',
+ 'analytics',
+ 'songs',
+ 'devops',
+ ];
+
+ return {
+ items: domainNames.map(
+ (n, i) =>
+ ({
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Domain',
+ metadata: {
+ name: n,
+ description: `Everything about ${n}`,
+ tags: i % 2 === 0 ? [n] : undefined,
+ },
+ spec: {
+ owner: `${n}@example.com`,
+ },
+ } as Entity),
+ ),
+ };
+ },
+ } as CatalogApi),
+ })
+ .render();
diff --git a/plugins/ecosystem/docs/domains.png b/plugins/ecosystem/docs/domains.png
new file mode 100644
index 0000000000..70f3be02d6
Binary files /dev/null and b/plugins/ecosystem/docs/domains.png differ
diff --git a/plugins/ecosystem/package.json b/plugins/ecosystem/package.json
new file mode 100644
index 0000000000..52b2db3d6a
--- /dev/null
+++ b/plugins/ecosystem/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "@backstage/plugin-ecosystem",
+ "version": "0.1.1",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "scripts": {
+ "build": "backstage-cli plugin:build",
+ "start": "backstage-cli plugin:serve",
+ "lint": "backstage-cli lint",
+ "test": "backstage-cli test",
+ "diff": "backstage-cli plugin:diff",
+ "prepack": "backstage-cli prepack",
+ "postpack": "backstage-cli postpack",
+ "clean": "backstage-cli clean"
+ },
+ "dependencies": {
+ "@backstage/catalog-model": "^0.6.1",
+ "@backstage/core": "^0.4.4",
+ "@backstage/plugin-catalog": "^0.2.10",
+ "@backstage/theme": "^0.2.2",
+ "@material-ui/core": "^4.11.0",
+ "@material-ui/icons": "^4.9.1",
+ "@material-ui/lab": "4.0.0-alpha.45",
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1",
+ "react-router-dom": "6.0.0-beta.0",
+ "react-use": "^15.3.3"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.4.6",
+ "@backstage/dev-utils": "^0.1.7",
+ "@backstage/test-utils": "^0.1.6",
+ "@testing-library/jest-dom": "^5.10.1",
+ "@testing-library/react": "^10.4.1",
+ "@testing-library/user-event": "^12.0.7",
+ "@types/jest": "^26.0.7",
+ "@types/node": "^12.0.0",
+ "msw": "^0.21.2",
+ "cross-fetch": "^3.0.6"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/ecosystem/src/components/DomainCard/DomainCard.test.tsx b/plugins/ecosystem/src/components/DomainCard/DomainCard.test.tsx
new file mode 100644
index 0000000000..e0d116269d
--- /dev/null
+++ b/plugins/ecosystem/src/components/DomainCard/DomainCard.test.tsx
@@ -0,0 +1,49 @@
+/*
+ * 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 { DomainEntity } from '@backstage/catalog-model';
+import { render } from '@testing-library/react';
+import React from 'react';
+import { MemoryRouter } from 'react-router-dom';
+import { DomainCard } from './DomainCard';
+
+describe('', () => {
+ it('renders a domain card', () => {
+ const entity: DomainEntity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Domain',
+ metadata: {
+ name: 'artists',
+ description: 'Everything about artists',
+ tags: ['a-tag'],
+ },
+ spec: {
+ owner: 'guest',
+ },
+ };
+ const { getByText } = render(, {
+ wrapper: MemoryRouter,
+ });
+
+ expect(getByText('artists')).toBeInTheDocument();
+ expect(getByText('Everything about artists')).toBeInTheDocument();
+ expect(getByText('a-tag')).toBeInTheDocument();
+ expect(getByText('Explore')).toHaveAttribute(
+ 'href',
+ '/catalog/default/domain/artists',
+ );
+ });
+});
diff --git a/plugins/ecosystem/src/components/DomainCard/DomainCard.tsx b/plugins/ecosystem/src/components/DomainCard/DomainCard.tsx
new file mode 100644
index 0000000000..78938ce641
--- /dev/null
+++ b/plugins/ecosystem/src/components/DomainCard/DomainCard.tsx
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 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 { DomainEntity } from '@backstage/catalog-model';
+import { ItemCard } from '@backstage/core';
+import { entityRoute, entityRouteParams } from '@backstage/plugin-catalog';
+import React from 'react';
+import { generatePath } from 'react-router-dom';
+
+type DomainCardProps = {
+ entity: DomainEntity;
+};
+
+export const DomainCard = ({ entity }: DomainCardProps) => (
+
+);
diff --git a/plugins/ecosystem/src/components/DomainCard/index.ts b/plugins/ecosystem/src/components/DomainCard/index.ts
new file mode 100644
index 0000000000..3511ff023d
--- /dev/null
+++ b/plugins/ecosystem/src/components/DomainCard/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2021 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.
+ */
+export { DomainCard } from './DomainCard';
diff --git a/plugins/ecosystem/src/components/DomainExplorer/DomainExplorer.tsx b/plugins/ecosystem/src/components/DomainExplorer/DomainExplorer.tsx
new file mode 100644
index 0000000000..92bdc4f14a
--- /dev/null
+++ b/plugins/ecosystem/src/components/DomainExplorer/DomainExplorer.tsx
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 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 { DomainEntity } from '@backstage/catalog-model';
+import { Grid } from '@material-ui/core';
+import React from 'react';
+import { DomainCard } from '../DomainCard';
+
+type DomainExplorerProps = {
+ entities: DomainEntity[];
+};
+
+export const DomainExplorer = ({ entities }: DomainExplorerProps) => (
+
+ {entities.map((e, i) => (
+
+
+
+ ))}
+
+);
diff --git a/plugins/ecosystem/src/components/DomainExplorer/index.ts b/plugins/ecosystem/src/components/DomainExplorer/index.ts
new file mode 100644
index 0000000000..48bb10220e
--- /dev/null
+++ b/plugins/ecosystem/src/components/DomainExplorer/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2021 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.
+ */
+export { DomainExplorer } from './DomainExplorer';
diff --git a/plugins/ecosystem/src/components/DomainExplorerPage/DomainExplorerPage.tsx b/plugins/ecosystem/src/components/DomainExplorerPage/DomainExplorerPage.tsx
new file mode 100644
index 0000000000..b8b6c9c72c
--- /dev/null
+++ b/plugins/ecosystem/src/components/DomainExplorerPage/DomainExplorerPage.tsx
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2021 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 { DomainEntity } from '@backstage/catalog-model';
+import {
+ configApiRef,
+ Content,
+ ContentHeader,
+ EmptyState,
+ Header,
+ Page,
+ Progress,
+ SupportButton,
+ useApi,
+} from '@backstage/core';
+import { catalogApiRef } from '@backstage/plugin-catalog';
+import { Button } from '@material-ui/core';
+import React from 'react';
+import { useAsync } from 'react-use';
+import { DomainExplorer } from '../DomainExplorer/DomainExplorer';
+
+export const DomainExplorerPage = () => {
+ const configApi = useApi(configApiRef);
+ const organizationName =
+ configApi.getOptionalString('organization.name') ?? 'Backstage';
+ const catalogApi = useApi(catalogApiRef);
+ const { value: entities, loading } = useAsync(async () => {
+ const response = await catalogApi.getEntities({
+ filter: { kind: 'domain' },
+ });
+
+ return response.items as DomainEntity[];
+ }, [catalogApi]);
+
+ return (
+
+
+
+
+ Discover the domains in your ecosystem.
+
+ {loading && }
+ {!loading && (!entities || entities.length === 0) && (
+
+ Read more
+
+ }
+ />
+ )}
+ {!loading && entities && }
+
+
+ );
+};
diff --git a/plugins/ecosystem/src/components/DomainExplorerPage/index.ts b/plugins/ecosystem/src/components/DomainExplorerPage/index.ts
new file mode 100644
index 0000000000..121eacdc99
--- /dev/null
+++ b/plugins/ecosystem/src/components/DomainExplorerPage/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2021 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.
+ */
+export { DomainExplorerPage } from './DomainExplorerPage';
diff --git a/plugins/ecosystem/src/index.ts b/plugins/ecosystem/src/index.ts
new file mode 100644
index 0000000000..4607d3b35d
--- /dev/null
+++ b/plugins/ecosystem/src/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2021 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.
+ */
+export { plugin } from './plugin';
diff --git a/plugins/ecosystem/src/plugin.test.ts b/plugins/ecosystem/src/plugin.test.ts
new file mode 100644
index 0000000000..dd151f9a0e
--- /dev/null
+++ b/plugins/ecosystem/src/plugin.test.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 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 { plugin } from './plugin';
+
+describe('ecosystem', () => {
+ it('should export plugin', () => {
+ expect(plugin).toBeDefined();
+ });
+});
diff --git a/plugins/ecosystem/src/plugin.ts b/plugins/ecosystem/src/plugin.ts
new file mode 100644
index 0000000000..3246c9f3c4
--- /dev/null
+++ b/plugins/ecosystem/src/plugin.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 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 { createPlugin, createRouteRef } from '@backstage/core';
+import { DomainExplorerPage } from './components/DomainExplorerPage';
+
+export const rootRouteRef = createRouteRef({
+ path: '/ecosystem',
+ title: 'ecosystem',
+});
+
+export const plugin = createPlugin({
+ id: 'ecosystem',
+ register({ router }) {
+ router.addRoute(rootRouteRef, DomainExplorerPage);
+ },
+});
diff --git a/plugins/ecosystem/src/setupTests.ts b/plugins/ecosystem/src/setupTests.ts
new file mode 100644
index 0000000000..0cec5b395d
--- /dev/null
+++ b/plugins/ecosystem/src/setupTests.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 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 '@testing-library/jest-dom';
+import 'cross-fetch/polyfill';