add namespace filter and column
Signed-off-by: David An <a-daan@expediagroup.com>
This commit is contained in:
@@ -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 = <CheckBoxOutlineBlankIcon fontSize="small" />;
|
||||
const checkedIcon = <CheckBoxIcon fontSize="small" />;
|
||||
|
||||
/** @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 (
|
||||
<Box pb={1} pt={1}>
|
||||
<Typography variant="button" component="label">
|
||||
Namespace
|
||||
<Autocomplete
|
||||
multiple
|
||||
options={availableNamespace}
|
||||
value={selectedNamespace}
|
||||
onChange={(_: object, value: string[]) => setSelectedNamespace(value)}
|
||||
renderOption={(option, { selected }) => (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={icon}
|
||||
checkedIcon={checkedIcon}
|
||||
checked={selected}
|
||||
/>
|
||||
}
|
||||
label={option}
|
||||
/>
|
||||
)}
|
||||
size="small"
|
||||
popupIcon={<ExpandMoreIcon data-testid="namespace-picker-expand" />}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
className={classes.input}
|
||||
variant="outlined"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
@@ -29,3 +29,4 @@ export * from './InspectEntityDialog';
|
||||
export * from './UnregisterEntityDialog';
|
||||
export * from './UserListPicker';
|
||||
export * from './EntityProcessingStatusPicker';
|
||||
export * from './EntityNamespacePicker';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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) {
|
||||
<EntityLifecyclePicker />
|
||||
<EntityTagPicker />
|
||||
<EntityProcessingStatusPicker />
|
||||
<EntityNamespacePicker />
|
||||
</CatalogFilterLayout.Filters>
|
||||
<CatalogFilterLayout.Content>
|
||||
<CatalogTable
|
||||
|
||||
@@ -89,6 +89,12 @@ export const CatalogTable = (props: CatalogTableProps) => {
|
||||
];
|
||||
|
||||
function createEntitySpecificColumns(): TableColumn<CatalogTableRow>[] {
|
||||
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
|
||||
|
||||
@@ -193,4 +193,11 @@ export const columnFactories = Object.freeze({
|
||||
width: 'auto',
|
||||
};
|
||||
},
|
||||
createNamespaceColumn(): TableColumn<CatalogTableRow> {
|
||||
return {
|
||||
title: 'Namespace',
|
||||
field: 'entity.metadata.namespace',
|
||||
width: 'auto',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user