diff --git a/plugins/catalog-react/src/components/EntityNamespacePicker/EntityNamespacePicker.tsx b/plugins/catalog-react/src/components/EntityNamespacePicker/EntityNamespacePicker.tsx
new file mode 100644
index 0000000000..30df7fc175
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityNamespacePicker/EntityNamespacePicker.tsx
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2021 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 { Entity } from '@backstage/catalog-model';
+import {
+ Box,
+ Checkbox,
+ FormControlLabel,
+ makeStyles,
+ TextField,
+ Typography,
+} from '@material-ui/core';
+import CheckBoxIcon from '@material-ui/icons/CheckBox';
+import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import { Autocomplete } from '@material-ui/lab';
+import React, { useEffect, useMemo, useState } from 'react';
+import { useEntityList } from '../../hooks/useEntityListProvider';
+import { EntityNamespaceFilter } from '../../filters';
+
+/** @public */
+export type CatalogReactEntityNamespacePickerClassKey = 'input';
+
+const useStyles = makeStyles(
+ {
+ input: {},
+ },
+ {
+ name: 'CatalogReactEntityNamespacePicker',
+ },
+);
+
+const icon = ;
+const checkedIcon = ;
+
+/** @public */
+export const EntityNamespacePicker = (props: { initialFilter?: string[] }) => {
+ const { initialFilter = [] } = props;
+ const classes = useStyles();
+ const {
+ updateFilters,
+ backendEntities,
+ filters,
+ queryParameters: { namespace: namespaceParameter },
+ } = useEntityList();
+
+ const queryParamNamespace = useMemo(
+ () => [namespaceParameter].flat().filter(Boolean) as string[],
+ [namespaceParameter],
+ );
+
+ const [selectedNamespace, setSelectedNamespace] = useState(
+ queryParamNamespace.length
+ ? queryParamNamespace
+ : filters.namespace?.values ?? initialFilter,
+ );
+
+ // Set selected namespace on query parameter updates; this happens at initial page load and from
+ // external updates to the page location.
+ useEffect(() => {
+ if (queryParamNamespace.length) {
+ setSelectedNamespace(queryParamNamespace);
+ }
+ }, [queryParamNamespace]);
+
+ const availableNamespace = useMemo(
+ () =>
+ [
+ ...new Set(
+ backendEntities
+ .map((e: Entity) => e.metadata.namespace)
+ .filter(Boolean) as string[],
+ ),
+ ].sort(),
+ [backendEntities],
+ );
+
+ useEffect(() => {
+ updateFilters({
+ namespace:
+ selectedNamespace.length && availableNamespace.length
+ ? new EntityNamespaceFilter(selectedNamespace)
+ : undefined,
+ });
+ }, [selectedNamespace, updateFilters, availableNamespace]);
+
+ if (!availableNamespace.length) return null;
+
+ if (availableNamespace.every(namespace => namespace === 'default'))
+ return null;
+
+ return (
+
+
+ Namespace
+ setSelectedNamespace(value)}
+ renderOption={(option, { selected }) => (
+
+ }
+ label={option}
+ />
+ )}
+ size="small"
+ popupIcon={}
+ renderInput={params => (
+
+ )}
+ />
+
+
+ );
+};
diff --git a/plugins/catalog-react/src/components/EntityNamespacePicker/index.ts b/plugins/catalog-react/src/components/EntityNamespacePicker/index.ts
new file mode 100644
index 0000000000..d08536ea2c
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityNamespacePicker/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 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 { EntityNamespacePicker } from './EntityNamespacePicker';
+export type { CatalogReactEntityNamespacePickerClassKey } from './EntityNamespacePicker';
diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts
index a4380ddef0..6d8aa393ed 100644
--- a/plugins/catalog-react/src/components/index.ts
+++ b/plugins/catalog-react/src/components/index.ts
@@ -29,3 +29,4 @@ export * from './InspectEntityDialog';
export * from './UnregisterEntityDialog';
export * from './UserListPicker';
export * from './EntityProcessingStatusPicker';
+export * from './EntityNamespacePicker';
diff --git a/plugins/catalog-react/src/filters.ts b/plugins/catalog-react/src/filters.ts
index 056b3b92c1..2fc2b3b382 100644
--- a/plugins/catalog-react/src/filters.ts
+++ b/plugins/catalog-react/src/filters.ts
@@ -149,6 +149,22 @@ export class EntityLifecycleFilter implements EntityFilter {
}
}
+/**
+ * Filters entities on namespace.
+ * @public
+ */
+export class EntityNamespaceFilter implements EntityFilter {
+ constructor(readonly values: string[]) {}
+
+ filterEntity(entity: Entity): boolean {
+ return this.values.some(v => entity.metadata.namespace === v);
+ }
+
+ toQueryValue(): string[] {
+ return this.values;
+ }
+}
+
/**
* Filters entities based on whatever the user has starred or owns them.
* @public
diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx
index b5dfb4d8f7..3a20c814c0 100644
--- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx
@@ -40,6 +40,7 @@ import {
EntityTextFilter,
EntityTypeFilter,
UserListFilter,
+ EntityNamespaceFilter,
} from '../filters';
import { EntityFilter } from '../types';
import { reduceCatalogFilters, reduceEntityFilters } from '../utils';
@@ -56,6 +57,7 @@ export type DefaultEntityFilters = {
text?: EntityTextFilter;
orphan?: EntityOrphanFilter;
error?: EntityErrorFilter;
+ namespace?: EntityNamespaceFilter;
};
/** @public */
diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx
index 644453f5fb..b0fb52b4f0 100644
--- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx
+++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx
@@ -35,6 +35,7 @@ import {
UserListFilterKind,
UserListPicker,
EntityKindPicker,
+ EntityNamespacePicker,
} from '@backstage/plugin-catalog-react';
import React, { ReactNode } from 'react';
import { createComponentRouteRef } from '../../routes';
@@ -90,6 +91,7 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) {
+
{
];
function createEntitySpecificColumns(): TableColumn[] {
+ const baseColumns = [
+ columnFactories.createSystemColumn(),
+ columnFactories.createOwnerColumn(),
+ columnFactories.createSpecTypeColumn(),
+ columnFactories.createSpecLifecycleColumn(),
+ ];
switch (filters.kind?.value) {
case 'user':
return [];
@@ -104,15 +110,14 @@ export const CatalogTable = (props: CatalogTableProps) => {
columnFactories.createSpecTargetsColumn(),
];
default:
- return [
- columnFactories.createSystemColumn(),
- columnFactories.createOwnerColumn(),
- columnFactories.createSpecTypeColumn(),
- columnFactories.createSpecLifecycleColumn(),
- ];
+ return entities.every(
+ entity => entity.metadata.namespace === 'default',
+ )
+ ? baseColumns
+ : [...baseColumns, columnFactories.createNamespaceColumn()];
}
}
- }, [filters.kind?.value]);
+ }, [filters.kind?.value, entities]);
const showTypeColumn = filters.type === undefined;
// TODO(timbonicus): remove the title from the CatalogTable once using EntitySearchBar
diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx
index 7f3cd5ef82..fc6e1aaf6b 100644
--- a/plugins/catalog/src/components/CatalogTable/columns.tsx
+++ b/plugins/catalog/src/components/CatalogTable/columns.tsx
@@ -193,4 +193,11 @@ export const columnFactories = Object.freeze({
width: 'auto',
};
},
+ createNamespaceColumn(): TableColumn {
+ return {
+ title: 'Namespace',
+ field: 'entity.metadata.namespace',
+ width: 'auto',
+ };
+ },
});