Merge branch 'master' into canon-website
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Creates new CatalogAutocomplete component in catalog-react that aligns with Select component UI for consistent a dropdown UI for all catalog filters.
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 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, { ComponentType } from 'react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { CatalogAutocomplete } from './CatalogAutocomplete';
|
||||
|
||||
export default {
|
||||
title: 'Catalog /CatalogAutocomplete',
|
||||
decorators: [(Story: ComponentType<{}>) => wrapInTestApp(<Story />)],
|
||||
};
|
||||
|
||||
export const Default = (args: any) => {
|
||||
return <CatalogAutocomplete {...args} />;
|
||||
};
|
||||
|
||||
Default.args = {
|
||||
multiple: true,
|
||||
label: 'Default',
|
||||
name: 'default',
|
||||
options: ['test 1', 'test 2', 'test 3'],
|
||||
};
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2024 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 from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { CatalogAutocomplete } from './CatalogAutocomplete';
|
||||
|
||||
describe('CatalogAutocomplete', () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOptions = ['Option 1', 'Option 2', 'Option 3'];
|
||||
|
||||
it('renders without exploding', () => {
|
||||
render(
|
||||
<CatalogAutocomplete
|
||||
name="test-autocomplete"
|
||||
options={mockOptions}
|
||||
label="Test Label"
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the expand icon', () => {
|
||||
render(
|
||||
<CatalogAutocomplete
|
||||
name="test-autocomplete"
|
||||
options={mockOptions}
|
||||
label="Test Label"
|
||||
/>,
|
||||
);
|
||||
const expandIcon = screen.getByTestId('test-autocomplete-expand');
|
||||
expect(expandIcon).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays options when clicked', async () => {
|
||||
render(
|
||||
<CatalogAutocomplete
|
||||
name="test-autocomplete"
|
||||
options={mockOptions}
|
||||
label="Test Label"
|
||||
/>,
|
||||
);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
await user.click(input);
|
||||
|
||||
mockOptions.forEach(option => {
|
||||
expect(screen.getByText(option)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('supports required input', () => {
|
||||
render(
|
||||
<CatalogAutocomplete
|
||||
name="test-autocomplete"
|
||||
options={mockOptions}
|
||||
label="Test Label"
|
||||
TextFieldProps={{ required: true }}
|
||||
/>,
|
||||
);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
expect(input).toBeRequired();
|
||||
});
|
||||
|
||||
it('displays helper text when provided', () => {
|
||||
render(
|
||||
<CatalogAutocomplete
|
||||
name="test-autocomplete"
|
||||
options={mockOptions}
|
||||
label="Test Label"
|
||||
TextFieldProps={{ helperText: 'Helper text' }}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Helper text')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders without label', () => {
|
||||
render(
|
||||
<CatalogAutocomplete name="test-autocomplete" options={mockOptions} />,
|
||||
);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
expect(input).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays correct option on selection', async () => {
|
||||
render(
|
||||
<CatalogAutocomplete
|
||||
name="test-autocomplete"
|
||||
options={mockOptions}
|
||||
label="Test Label"
|
||||
/>,
|
||||
);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
await user.click(input);
|
||||
|
||||
const optionToSelect = screen.getByText('Option 1');
|
||||
await user.click(optionToSelect);
|
||||
|
||||
expect(input).toHaveValue('Option 1');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2024 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 Box from '@material-ui/core/Box';
|
||||
import Typography, { TypographyProps } from '@material-ui/core/Typography';
|
||||
import Paper, { PaperProps } from '@material-ui/core/Paper';
|
||||
import Popper, { PopperProps } from '@material-ui/core/Popper';
|
||||
import TextField, { OutlinedTextFieldProps } from '@material-ui/core/TextField';
|
||||
import Grow from '@material-ui/core/Grow';
|
||||
import {
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
} from '@material-ui/core/styles';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import Autocomplete, {
|
||||
AutocompleteProps,
|
||||
AutocompleteRenderInputParams,
|
||||
} from '@material-ui/lab/Autocomplete';
|
||||
import React, { ReactNode, useCallback } from 'react';
|
||||
import { merge } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
root: {
|
||||
margin: theme.spacing(1, 0),
|
||||
},
|
||||
label: {
|
||||
position: 'relative',
|
||||
fontWeight: 'bold',
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
color: theme.palette.text.primary,
|
||||
'& > span': {
|
||||
top: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ name: 'BackstageAutocomplete' },
|
||||
);
|
||||
|
||||
const BootstrapAutocomplete = withStyles(
|
||||
(theme: Theme) =>
|
||||
createStyles({
|
||||
root: {},
|
||||
paper: {
|
||||
margin: 0,
|
||||
},
|
||||
hasClearIcon: {},
|
||||
hasPopupIcon: {},
|
||||
focused: {},
|
||||
inputRoot: {
|
||||
marginTop: 24,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
'$root$hasClearIcon$hasPopupIcon &': {
|
||||
paddingBlock: theme.spacing(0.75),
|
||||
paddingInlineStart: theme.spacing(0.75),
|
||||
},
|
||||
'$root$focused &': {
|
||||
outline: 'none',
|
||||
},
|
||||
'$root &:hover > fieldset': {
|
||||
borderColor: '#ced4da',
|
||||
},
|
||||
'$root$focused & > fieldset': {
|
||||
borderWidth: 1,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
popupIndicator: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
color: '#616161',
|
||||
'&:hover': {
|
||||
backgroundColor: 'unset',
|
||||
},
|
||||
'& [class*="MuiTouchRipple-root"]': {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
endAdornment: {
|
||||
'$root$hasClearIcon$hasPopupIcon &': {
|
||||
right: 4,
|
||||
},
|
||||
},
|
||||
input: {
|
||||
'$root$hasClearIcon$hasPopupIcon &': {
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
paddingBlock: theme.spacing(0.8125),
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ name: 'BackstageAutocompleteBase' },
|
||||
)(Autocomplete) as typeof Autocomplete;
|
||||
|
||||
const PopperComponent = (props: PopperProps) => (
|
||||
<Popper {...props} transition placement="bottom-start">
|
||||
{({ TransitionProps }) => (
|
||||
<Grow {...TransitionProps} style={{ transformOrigin: '0 0 0' }}>
|
||||
<Box>{props.children as ReactNode}</Box>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
);
|
||||
|
||||
const PaperComponent = (props: PaperProps) => (
|
||||
<Paper {...props} elevation={8} />
|
||||
);
|
||||
|
||||
export type CatalogAutocompleteProps<
|
||||
T,
|
||||
Multiple extends boolean | undefined = undefined,
|
||||
DisableClearable extends boolean | undefined = undefined,
|
||||
FreeSolo extends boolean | undefined = undefined,
|
||||
> = Omit<
|
||||
AutocompleteProps<T, Multiple, DisableClearable, FreeSolo>,
|
||||
'PopperComponent' | 'PaperComponent' | 'popupIcon' | 'renderInput'
|
||||
> & {
|
||||
name: string;
|
||||
label?: string;
|
||||
LabelProps?: TypographyProps<'label'>;
|
||||
TextFieldProps?: Omit<OutlinedTextFieldProps, 'variant'>;
|
||||
renderInput?: AutocompleteProps<
|
||||
T,
|
||||
Multiple,
|
||||
DisableClearable,
|
||||
FreeSolo
|
||||
>['renderInput'];
|
||||
};
|
||||
|
||||
export function CatalogAutocomplete<
|
||||
T,
|
||||
Multiple extends boolean | undefined = undefined,
|
||||
DisableClearable extends boolean | undefined = undefined,
|
||||
FreeSolo extends boolean | undefined = undefined,
|
||||
>(props: CatalogAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>) {
|
||||
const { label, name, LabelProps, TextFieldProps, ...rest } = props;
|
||||
const classes = useStyles();
|
||||
const renderInput = useCallback(
|
||||
(params: AutocompleteRenderInputParams) => (
|
||||
<TextField {...merge(params, TextFieldProps)} variant="outlined" />
|
||||
),
|
||||
[TextFieldProps],
|
||||
);
|
||||
const autocomplete = (
|
||||
<BootstrapAutocomplete
|
||||
size="small"
|
||||
{...rest}
|
||||
renderInput={rest.renderInput ?? renderInput}
|
||||
popupIcon={<ExpandMoreIcon data-testid={`${name}-expand`} />}
|
||||
PaperComponent={PaperComponent}
|
||||
PopperComponent={PopperComponent}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box className={classes.root}>
|
||||
{label ? (
|
||||
<Typography
|
||||
{...LabelProps}
|
||||
className={classNames(classes.label, LabelProps?.className)}
|
||||
component="label"
|
||||
>
|
||||
<Box component="span">{label}</Box>
|
||||
{autocomplete}
|
||||
</Typography>
|
||||
) : (
|
||||
autocomplete
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
CatalogAutocomplete,
|
||||
type CatalogAutocompleteProps,
|
||||
} from './CatalogAutocomplete';
|
||||
+22
-36
@@ -16,22 +16,19 @@
|
||||
|
||||
import Box from '@material-ui/core/Box';
|
||||
import { TextFieldProps } from '@material-ui/core/TextField';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import React, { useEffect, useMemo, useState, ReactNode } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { catalogApiRef } from '../../api';
|
||||
import { EntityAutocompletePickerOption } from './EntityAutocompletePickerOption';
|
||||
import { EntityAutocompletePickerInput } from './EntityAutocompletePickerInput';
|
||||
import {
|
||||
DefaultEntityFilters,
|
||||
useEntityList,
|
||||
} from '../../hooks/useEntityListProvider';
|
||||
import { EntityFilter } from '../../types';
|
||||
import { reduceBackendCatalogFilters } from '../../utils/filters';
|
||||
import { CatalogAutocomplete } from '../CatalogAutocomplete';
|
||||
|
||||
/** @public */
|
||||
export type AllowedEntityFilters<T extends DefaultEntityFilters> = {
|
||||
@@ -86,7 +83,6 @@ export function EntityAutocompletePicker<
|
||||
initialSelectedOptions = [],
|
||||
filtersForAvailableValues = ['kind'],
|
||||
} = props;
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const {
|
||||
@@ -152,36 +148,26 @@ export function EntityAutocompletePicker<
|
||||
|
||||
return (
|
||||
<Box className={classes.root} pb={1} pt={1}>
|
||||
<Typography className={classes.label} variant="button" component="label">
|
||||
{label}
|
||||
<Autocomplete<string, true>
|
||||
PopperComponent={popperProps => (
|
||||
<div {...popperProps}>{popperProps.children as ReactNode}</div>
|
||||
)}
|
||||
multiple
|
||||
disableCloseOnSelect
|
||||
options={availableOptions}
|
||||
value={selectedOptions}
|
||||
onChange={(_event: object, options: string[]) =>
|
||||
setSelectedOptions(options)
|
||||
}
|
||||
renderOption={(option, { selected }) => (
|
||||
<EntityAutocompletePickerOption
|
||||
selected={selected}
|
||||
value={option}
|
||||
availableOptions={availableValues}
|
||||
showCounts={!!showCounts}
|
||||
/>
|
||||
)}
|
||||
size="small"
|
||||
popupIcon={
|
||||
<ExpandMoreIcon data-testid={`${String(name)}-picker-expand`} />
|
||||
}
|
||||
renderInput={params => (
|
||||
<EntityAutocompletePickerInput {...params} {...InputProps} />
|
||||
)}
|
||||
/>
|
||||
</Typography>
|
||||
<CatalogAutocomplete<string, true>
|
||||
multiple
|
||||
disableCloseOnSelect
|
||||
label={label}
|
||||
name={`${String(name)}-picker`}
|
||||
options={availableOptions}
|
||||
value={selectedOptions}
|
||||
TextFieldProps={InputProps}
|
||||
onChange={(_event: object, options: string[]) =>
|
||||
setSelectedOptions(options)
|
||||
}
|
||||
renderOption={(option, { selected }) => (
|
||||
<EntityAutocompletePickerOption
|
||||
selected={selected}
|
||||
value={option}
|
||||
availableOptions={availableValues}
|
||||
showCounts={!!showCounts}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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 TextField, { TextFieldProps } from '@material-ui/core/TextField';
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const useStyles = makeStyles(
|
||||
(theme: Theme) =>
|
||||
createStyles({
|
||||
input: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'CatalogReactEntityAutocompletePickerInput',
|
||||
},
|
||||
);
|
||||
|
||||
export function EntityAutocompletePickerInput(params: TextFieldProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<TextField
|
||||
variant="outlined"
|
||||
{...params}
|
||||
className={classnames(classes.input, params.className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -22,15 +22,12 @@ import {
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
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/Autocomplete';
|
||||
import React, { useEffect, useMemo, useState, ReactNode } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useEntityList } from '../../hooks/useEntityListProvider';
|
||||
import { EntityOwnerFilter } from '../../filters';
|
||||
import { useDebouncedEffect } from '@react-hookz/web';
|
||||
@@ -42,29 +39,23 @@ import { withStyles } from '@material-ui/core/styles';
|
||||
import { useEntityPresentation } from '../../apis';
|
||||
import { catalogReactTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { PopperProps } from '@material-ui/core/Popper';
|
||||
import { CatalogAutocomplete } from '../CatalogAutocomplete';
|
||||
|
||||
/** @public */
|
||||
export type CatalogReactEntityOwnerPickerClassKey = 'input';
|
||||
|
||||
const useStyles = makeStyles(
|
||||
(theme: Theme) =>
|
||||
createStyles({
|
||||
root: {},
|
||||
label: {
|
||||
textTransform: 'none',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
input: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
fullWidth: { width: '100%' },
|
||||
boxLabel: {
|
||||
width: '100%',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
}),
|
||||
{
|
||||
root: {},
|
||||
label: {},
|
||||
input: {},
|
||||
fullWidth: { width: '100%' },
|
||||
boxLabel: {
|
||||
width: '100%',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
},
|
||||
{ name: 'CatalogReactEntityOwnerPicker' },
|
||||
);
|
||||
|
||||
@@ -147,7 +138,7 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => {
|
||||
[ownersParameter],
|
||||
);
|
||||
|
||||
const [selectedOwners, setSelectedOwners] = useState(
|
||||
const [selectedOwners, setSelectedOwners] = useState<string[]>(
|
||||
queryParamOwners.length ? queryParamOwners : filters.owners?.values ?? [],
|
||||
);
|
||||
|
||||
@@ -186,84 +177,69 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => {
|
||||
|
||||
return (
|
||||
<Box className={classes.root} pb={1} pt={1}>
|
||||
<Typography className={classes.label} variant="button" component="label">
|
||||
{t('entityOwnerPicker.title')}
|
||||
<Autocomplete
|
||||
PopperComponent={Popper}
|
||||
multiple
|
||||
disableCloseOnSelect
|
||||
loading={loading}
|
||||
options={availableOwners}
|
||||
value={selectedOwners as unknown as Entity[]}
|
||||
getOptionSelected={(o, v) => {
|
||||
if (typeof v === 'string') {
|
||||
return stringifyEntityRef(o) === v;
|
||||
}
|
||||
return o === v;
|
||||
}}
|
||||
getOptionLabel={o => {
|
||||
const entity =
|
||||
typeof o === 'string'
|
||||
? cache.getEntity(o) ||
|
||||
parseEntityRef(o, {
|
||||
defaultKind: 'group',
|
||||
defaultNamespace: 'default',
|
||||
})
|
||||
: o;
|
||||
return humanizeEntity(entity, humanizeEntityRef(entity));
|
||||
}}
|
||||
onChange={(_: object, owners) => {
|
||||
setText('');
|
||||
setSelectedOwners(
|
||||
owners.map(e => {
|
||||
const entityRef =
|
||||
typeof e === 'string' ? e : stringifyEntityRef(e);
|
||||
<CatalogAutocomplete<Entity, true>
|
||||
label={t('entityOwnerPicker.title')}
|
||||
multiple
|
||||
disableCloseOnSelect
|
||||
loading={loading}
|
||||
options={availableOwners}
|
||||
value={selectedOwners as unknown as Entity[]}
|
||||
getOptionSelected={(o, v) => {
|
||||
if (typeof v === 'string') {
|
||||
return stringifyEntityRef(o) === v;
|
||||
}
|
||||
return o === v;
|
||||
}}
|
||||
getOptionLabel={o => {
|
||||
const entity =
|
||||
typeof o === 'string'
|
||||
? cache.getEntity(o) ||
|
||||
parseEntityRef(o, {
|
||||
defaultKind: 'group',
|
||||
defaultNamespace: 'default',
|
||||
})
|
||||
: o;
|
||||
return humanizeEntity(entity, humanizeEntityRef(entity));
|
||||
}}
|
||||
onChange={(_: object, owners) => {
|
||||
setText('');
|
||||
setSelectedOwners(
|
||||
owners.map(e => {
|
||||
const entityRef =
|
||||
typeof e === 'string' ? e : stringifyEntityRef(e);
|
||||
|
||||
if (typeof e !== 'string') {
|
||||
cache.setEntity(e);
|
||||
}
|
||||
return entityRef;
|
||||
}),
|
||||
);
|
||||
}}
|
||||
filterOptions={x => x}
|
||||
renderOption={(entity, { selected }) => {
|
||||
return <RenderOptionLabel entity={entity} isSelected={selected} />;
|
||||
}}
|
||||
size="small"
|
||||
popupIcon={<ExpandMoreIcon data-testid="owner-picker-expand" />}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
className={classes.input}
|
||||
onChange={e => {
|
||||
setText(e.currentTarget.value);
|
||||
}}
|
||||
variant="outlined"
|
||||
/>
|
||||
)}
|
||||
ListboxProps={{
|
||||
onScroll: (e: React.MouseEvent) => {
|
||||
const element = e.currentTarget;
|
||||
const hasReachedEnd =
|
||||
Math.abs(
|
||||
element.scrollHeight -
|
||||
element.clientHeight -
|
||||
element.scrollTop,
|
||||
) < 1;
|
||||
|
||||
if (hasReachedEnd && value?.cursor) {
|
||||
handleFetch({ items: value.items, cursor: value.cursor });
|
||||
if (typeof e !== 'string') {
|
||||
cache.setEntity(e);
|
||||
}
|
||||
},
|
||||
'data-testid': 'owner-picker-listbox',
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
return entityRef;
|
||||
}),
|
||||
);
|
||||
}}
|
||||
filterOptions={x => x}
|
||||
renderOption={(entity, { selected }) => {
|
||||
return <RenderOptionLabel entity={entity} isSelected={selected} />;
|
||||
}}
|
||||
name="owner-picker"
|
||||
onInputChange={(_e, inputValue) => {
|
||||
setText(inputValue);
|
||||
}}
|
||||
ListboxProps={{
|
||||
onScroll: (e: React.MouseEvent) => {
|
||||
const element = e.currentTarget;
|
||||
const hasReachedEnd =
|
||||
Math.abs(
|
||||
element.scrollHeight - element.clientHeight - element.scrollTop,
|
||||
) < 1;
|
||||
|
||||
if (hasReachedEnd && value?.cursor) {
|
||||
handleFetch({ items: value.items, cursor: value.cursor });
|
||||
}
|
||||
},
|
||||
'data-testid': 'owner-picker-listbox',
|
||||
}}
|
||||
LabelProps={{ className: classes.label }}
|
||||
TextFieldProps={{ className: classes.input }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
function Popper({ children }: PopperProps) {
|
||||
return <div>{children as ReactNode}</div>;
|
||||
}
|
||||
|
||||
+36
-58
@@ -18,33 +18,24 @@ import { EntityErrorFilter, EntityOrphanFilter } from '../../filters';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import CheckBoxIcon from '@material-ui/icons/CheckBox';
|
||||
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import React, { useState, ReactNode } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useEntityList } from '../../hooks';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import { catalogReactTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { CatalogAutocomplete } from '../CatalogAutocomplete';
|
||||
|
||||
/** @public */
|
||||
export type CatalogReactEntityProcessingStatusPickerClassKey = 'input';
|
||||
|
||||
const useStyles = makeStyles(
|
||||
(theme: Theme) =>
|
||||
createStyles({
|
||||
root: {},
|
||||
input: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
label: {
|
||||
textTransform: 'none',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
}),
|
||||
{
|
||||
root: {},
|
||||
input: {},
|
||||
label: {},
|
||||
},
|
||||
{ name: 'CatalogReactEntityProcessingStatusPickerPicker' },
|
||||
);
|
||||
|
||||
@@ -77,47 +68,34 @@ export const EntityProcessingStatusPicker = () => {
|
||||
|
||||
return (
|
||||
<Box className={classes.root} pb={1} pt={1}>
|
||||
<Typography className={classes.label} variant="button" component="label">
|
||||
{t('entityProcessingStatusPicker.title')}
|
||||
<Autocomplete<string, true>
|
||||
PopperComponent={popperProps => (
|
||||
<div {...popperProps}>{popperProps.children as ReactNode}</div>
|
||||
)}
|
||||
multiple
|
||||
disableCloseOnSelect
|
||||
options={availableAdvancedItems}
|
||||
value={selectedAdvancedItems}
|
||||
onChange={(_: object, value: string[]) => {
|
||||
setSelectedAdvancedItems(value);
|
||||
orphanChange(value.includes('Is Orphan'));
|
||||
errorChange(value.includes('Has Error'));
|
||||
}}
|
||||
renderOption={(option, { selected }) => (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={icon}
|
||||
checkedIcon={checkedIcon}
|
||||
checked={selected}
|
||||
/>
|
||||
}
|
||||
onClick={event => event.preventDefault()}
|
||||
label={option}
|
||||
/>
|
||||
)}
|
||||
size="small"
|
||||
popupIcon={
|
||||
<ExpandMoreIcon data-testid="processing-status-picker-expand" />
|
||||
}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
className={classes.input}
|
||||
variant="outlined"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Typography>
|
||||
<CatalogAutocomplete<string, true>
|
||||
label={t('entityProcessingStatusPicker.title')}
|
||||
multiple
|
||||
disableCloseOnSelect
|
||||
options={availableAdvancedItems}
|
||||
value={selectedAdvancedItems}
|
||||
onChange={(_: object, value: string[]) => {
|
||||
setSelectedAdvancedItems(value);
|
||||
orphanChange(value.includes('Is Orphan'));
|
||||
errorChange(value.includes('Has Error'));
|
||||
}}
|
||||
renderOption={(option, { selected }) => (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={icon}
|
||||
checkedIcon={checkedIcon}
|
||||
checked={selected}
|
||||
/>
|
||||
}
|
||||
onClick={event => event.preventDefault()}
|
||||
label={option}
|
||||
/>
|
||||
)}
|
||||
name="processing-status-picker"
|
||||
LabelProps={{ className: classes.label }}
|
||||
TextFieldProps={{ className: classes.input }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user