Merge remote-tracking branch 'origin/master' into enhance-schema-validator

This commit is contained in:
bogdannechyporenko
2023-02-12 19:17:17 +01:00
14 changed files with 109 additions and 17 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-aws': patch
---
Update to import `LocationSpec` from the correct package.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Fixed Webpack loader patterns so that they properly match the file suffix only.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search': patch
---
Implement a `showCounts` option to display result counts per type in `SearchType.Accordion`
@@ -79,6 +79,7 @@ const SearchPage = () => {
<SearchType.Accordion
name="Result Type"
defaultValue="software-catalog"
showCounts
types={[
{
value: 'software-catalog',
+5 -4
View File
@@ -107,7 +107,7 @@ export const transforms = (options: TransformOptions): Transforms => {
],
},
{
test: /\.(js|mjs|cjs)/,
test: /\.(js|mjs|cjs)$/,
resolve: {
fullySpecified: false,
},
@@ -141,9 +141,10 @@ export const transforms = (options: TransformOptions): Transforms => {
/\.gif$/,
/\.jpe?g$/,
/\.png$/,
/\.frag/,
{ and: [/\.svg/, { not: [/\.icon\.svg/] }] },
/\.xml/,
/\.frag$/,
/\.vert$/,
{ and: [/\.svg$/, { not: [/\.icon\.svg$/] }] },
/\.xml$/,
],
type: 'asset/resource',
generator: {
@@ -11,7 +11,7 @@ import { Config } from '@backstage/config';
import { Credentials } from 'aws-sdk';
import { EntityProvider } from '@backstage/plugin-catalog-backend';
import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
import { LocationSpec } from '@backstage/plugin-catalog-backend';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import { Logger } from 'winston';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { TaskRunner } from '@backstage/backend-tasks';
@@ -20,10 +20,12 @@ import aws from 'aws-sdk';
describe('AwsEKSClusterProcessor', () => {
AWSMock.setSDKInstance(aws);
describe('readLocation', () => {
const processor = new (AwsEKSClusterProcessor as any)({});
const location = { type: 'aws-eks', target: '957140518395/us-west-2' };
const emit = jest.fn();
it('generates cluster correctly', async () => {
const clusters: aws.EKS.Types.ListClustersResponse = {
clusters: ['backstage-test'],
@@ -40,11 +42,12 @@ describe('AwsEKSClusterProcessor', () => {
},
},
};
AWSMock.mock('EKS', 'listClusters', clusters);
AWSMock.mock('EKS', 'listClusters', clusters);
AWSMock.mock('EKS', 'describeCluster', cluster);
await processor.readLocation(location, false, emit);
expect(emit).toHaveBeenCalledWith({
type: 'entity',
location,
@@ -13,16 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
CatalogProcessor,
CatalogProcessorEmit,
LocationSpec,
} from '@backstage/plugin-catalog-backend';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import {
ANNOTATION_KUBERNETES_API_SERVER,
ANNOTATION_KUBERNETES_API_SERVER_CA,
ANNOTATION_KUBERNETES_AUTH_PROVIDER,
} from '@backstage/catalog-model';
} from '@backstage/plugin-kubernetes-common';
import { Credentials, EKS } from 'aws-sdk';
import { AWSCredentialFactory } from '../types';
@@ -19,9 +19,9 @@ import { Config } from '@backstage/config';
import {
CatalogProcessor,
CatalogProcessorEmit,
LocationSpec,
processingResult,
} from '@backstage/plugin-catalog-backend';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import AWS, { Credentials, Organizations } from 'aws-sdk';
import { Account, ListAccountsResponse } from 'aws-sdk/clients/organizations';
import { Logger } from 'winston';
@@ -20,9 +20,9 @@ import {
CatalogProcessor,
CatalogProcessorEmit,
CatalogProcessorParser,
LocationSpec,
processingResult,
} from '@backstage/plugin-catalog-backend';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import limiterFactory from 'p-limit';
/**
@@ -20,9 +20,9 @@ import { AwsS3Integration, ScmIntegrations } from '@backstage/integration';
import {
EntityProvider,
EntityProviderConnection,
LocationSpec,
locationSpecToLocationEntity,
} from '@backstage/plugin-catalog-backend';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import { AwsCredentials } from '../credentials/AwsCredentials';
import { readAwsS3Configs } from './config';
import { AwsS3Config } from './types';
+1
View File
@@ -97,6 +97,7 @@ export type SearchTypeAccordionProps = {
icon: JSX.Element;
}>;
defaultValue?: string;
showCounts?: boolean;
};
// @public
@@ -16,7 +16,7 @@
import React from 'react';
import { TestApiProvider } from '@backstage/test-utils';
import { act, render } from '@testing-library/react';
import { act, render, waitFor } from '@testing-library/react';
import user from '@testing-library/user-event';
import {
searchApiRef,
@@ -34,6 +34,8 @@ jest.mock('@backstage/plugin-search-react', () => ({
setTypes: (types: any) => setTypesMock(types),
pageCursor: '',
setPageCursor: (pageCursor: any) => setPageCursorMock(pageCursor),
term: 'abc',
filters: { foo: 'bar' },
}),
}));
@@ -48,7 +50,7 @@ describe('SearchType.Accordion', () => {
};
beforeEach(() => {
query.mockResolvedValue({ results: [] });
query.mockResolvedValue({ results: [], numberOfResults: 1234 });
});
const Wrapper = ({ children }: { children: React.ReactNode }) => {
@@ -132,4 +134,34 @@ describe('SearchType.Accordion', () => {
expect(queryByText('Collapse')).not.toBeInTheDocument();
});
it('should show result counts if enabled', async () => {
const { getAllByText } = render(
<Wrapper>
<SearchType.Accordion
name={expectedLabel}
showCounts
types={[expectedType]}
/>
</Wrapper>,
);
expect(query).toHaveBeenCalledWith({
term: 'abc',
types: [],
filters: { foo: 'bar' },
pageLimit: 0,
});
expect(query).toHaveBeenCalledWith({
term: 'abc',
types: [expectedType.value],
filters: {},
pageLimit: 0,
});
await waitFor(() => {
const countLabels = getAllByText('1234 results');
expect(countLabels.length).toEqual(2);
expect(countLabels[0]).toBeInTheDocument();
});
});
});
@@ -15,7 +15,8 @@
*/
import React, { cloneElement, Fragment, useEffect, useState } from 'react';
import { useSearch } from '@backstage/plugin-search-react';
import { useApi } from '@backstage/core-plugin-api';
import { searchApiRef, useSearch } from '@backstage/plugin-search-react';
import {
Accordion,
AccordionSummary,
@@ -32,6 +33,7 @@ import {
} from '@material-ui/core';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import AllIcon from '@material-ui/icons/FontDownload';
import useAsync from 'react-use/lib/useAsync';
const useStyles = makeStyles(theme => ({
card: {
@@ -81,13 +83,15 @@ export type SearchTypeAccordionProps = {
icon: JSX.Element;
}>;
defaultValue?: string;
showCounts?: boolean;
};
export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => {
const classes = useStyles();
const { setPageCursor, setTypes, types } = useSearch();
const { filters, setPageCursor, setTypes, term, types } = useSearch();
const searchApi = useApi(searchApiRef);
const [expanded, setExpanded] = useState(true);
const { defaultValue, name, types: givenTypes } = props;
const { defaultValue, name, showCounts, types: givenTypes } = props;
const toggleExpanded = () => setExpanded(prevState => !prevState);
const handleClick = (type: string) => {
@@ -116,6 +120,37 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => {
];
const selected = types[0] || '';
const { value: resultCounts } = useAsync(async () => {
if (!showCounts) {
return {};
}
const counts = await Promise.all(
definedTypes
.map(t => t.value)
.map(async type => {
const { numberOfResults } = await searchApi.query({
term,
types: type ? [type] : [],
filters:
types.includes(type) || (!types.length && !type) ? filters : {},
pageLimit: 0,
});
return [
type,
numberOfResults !== undefined
? `${
numberOfResults >= 10000 ? `>10000` : numberOfResults
} results`
: ' -- ',
];
}),
);
return Object.fromEntries(counts);
}, [filters, showCounts, term, types]);
return (
<Card className={classes.card}>
<CardHeader title={name} titleTypographyProps={{ variant: 'overline' }} />
@@ -161,7 +196,10 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => {
className: classes.listItemIcon,
})}
</ListItemIcon>
<ListItemText primary={type.name} />
<ListItemText
primary={type.name}
secondary={resultCounts && resultCounts[type.value]}
/>
</ListItem>
</Fragment>
))}