Added support for filter using config instead of props

Signed-off-by: shmaram <shaharshmaram@gmail.com>
This commit is contained in:
shmaram
2023-12-05 17:50:28 +02:00
parent 8e1a0aa867
commit 6aa6f66e72
8 changed files with 197 additions and 15 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
---
'@backstage/plugin-home': minor
'@backstage/plugin-home': patch
---
Added filter support for HomePageVisitedByType in order to enable filtering entities from the list
+28
View File
@@ -325,6 +325,34 @@ export default app.createRoot(
);
```
You can filter the items that are shown in the component.
this can be done by using the config file.
Filtering is done by using 3 parameters:
- `field` - define which field to filter. can be one of the following
- id: string;
- name: string;
- pathname: string;
- hits: number;
- timestamp: number;
- entityRef?: string;
- `operator` - can be one of the following `'<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'`
- `value` - the value of the filter
```yaml
home:
recentVisits:
filterBy:
- field:
operator:
value:
topVisits:
filterBy:
- field:
operator:
value:
```
## Contributing
### Homepage Components
-1
View File
@@ -235,7 +235,6 @@ export type VisitedByTypeProps = {
numVisitsTotal?: number;
loading?: boolean;
kind: VisitedByTypeKind;
filterBy?: VisitsApiQueryParams['filterBy'];
};
// @public
+68
View File
@@ -0,0 +1,68 @@
/*
* Copyright 2020 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 interface Config {
home?: {
/**
* Top visited plugin
* @visibility frontend
*/
topVisits?: {
/**
* Filter By config
* @visibility frontend
*/
filterBy?: Array<{
/**
* @visibility frontend
*/
field: string;
/**
* @visibility frontend
*/
operator: string;
/**
* @visibility frontend
*/
value: string | number;
}>;
};
/**
* Recent visited plugin
* @visibility frontend
*/
recentVisits?: {
/**
* Filter By config
* @visibility frontend
*/
filterBy?: Array<{
/**
* @visibility frontend
*/
field: string;
/**
* @visibility frontend
*/
operator: string;
/**
* @visibility frontend
*/
value: string | number;
}>;
};
};
}
+3 -1
View File
@@ -36,6 +36,7 @@
]
}
},
"configSchema": "config.d.ts",
"sideEffects": false,
"scripts": {
"build": "backstage-cli package build",
@@ -90,6 +91,7 @@
"msw": "^1.0.0"
},
"files": [
"dist"
"dist",
"config.d.ts"
]
}
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright 2023 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 { Visit, VisitsApiQueryParams } from './VisitsApi';
import { Config } from '@backstage/config';
/**
* Reads a single FilterBy config.
*
* @param config - The single config object
*
* @public
*/
export function readFilterByConfig(config: Config): {
field: keyof Visit;
operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains';
value: string | number;
} {
try {
const field = config.getString('field') as keyof Visit;
const operator = config.getString('operator') as
| '<'
| '<='
| '=='
| '!='
| '>'
| '>='
| 'contains';
const value = config.getString('value');
return { field, operator, value };
} catch (error) {
throw new Error(`Invalid config, ${error}`);
}
}
/**
* Reads a set of filter by configs.
*
* @param configs - All of the config objects
*
* @public
*/
export function readFilterByConfigs(
configs: Config[],
): VisitsApiQueryParams['filterBy'] {
return configs.map(readFilterByConfig);
}
@@ -16,10 +16,15 @@
import React from 'react';
import { Content } from './Content';
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
import {
TestApiProvider,
renderInTestApp,
MockConfigApi,
} from '@backstage/test-utils';
import { visitsApiRef } from '../../api';
import { ContextProvider } from './Context';
import { waitFor } from '@testing-library/react';
import { configApiRef } from '@backstage/core-plugin-api';
const visits = [
{
@@ -119,15 +124,29 @@ describe('<Content kind="recent"/>', () => {
});
it('allows items to be filtered', async () => {
const configApiMock = new MockConfigApi({
home: {
topVisits: {
filterBy: [
{
field: 'pathname',
operator: '==',
value: '/explore',
},
],
},
},
});
const { getByText, queryByText } = await renderInTestApp(
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
<TestApiProvider
apis={[
[configApiRef, configApiMock],
[visitsApiRef, mockVisitsApi],
]}
>
<ContextProvider>
<Content
kind="recent"
filterBy={[
{ field: 'pathname', operator: '==', value: '/explore' },
]}
/>
<Content kind="recent" />
</ContextProvider>
</TestApiProvider>,
);
@@ -15,10 +15,11 @@
*/
import React, { useEffect } from 'react';
import { readFilterByConfigs } from '../../api/config';
import { VisitedByType } from './VisitedByType';
import { Visit, VisitsApiQueryParams, visitsApiRef } from '../../api';
import { Visit, visitsApiRef } from '../../api';
import { ContextValueOnly, useContext } from './Context';
import { useApi } from '@backstage/core-plugin-api';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import useAsync from 'react-use/lib/useAsync';
/** @public */
@@ -31,7 +32,6 @@ export type VisitedByTypeProps = {
numVisitsTotal?: number;
loading?: boolean;
kind: VisitedByTypeKind;
filterBy?: VisitsApiQueryParams['filterBy'];
};
/**
@@ -44,7 +44,6 @@ export const Content = ({
numVisitsTotal,
loading,
kind,
filterBy,
}: VisitedByTypeProps) => {
const { setContext, setVisits, setLoading } = useContext();
// Allows behavior override from properties
@@ -62,10 +61,14 @@ export const Content = ({
setContext(state => ({ ...state, ...context }));
}, [setContext, kind, visits, loading, numVisitsOpen, numVisitsTotal]);
const config = useApi(configApiRef);
// Fetches data from visitsApi in case visits and loading are not provided
const visitsApi = useApi(visitsApiRef);
const { loading: reqLoading } = useAsync(async () => {
if (!visits && !loading && kind === 'recent') {
const filterBy = readFilterByConfigs(
config.getOptionalConfigArray('home.recentVisits.filterBy') ?? [],
);
return await visitsApi
.list({
limit: numVisitsTotal ?? 8,
@@ -75,6 +78,9 @@ export const Content = ({
.then(setVisits);
}
if (!visits && !loading && kind === 'top') {
const filterBy = readFilterByConfigs(
config.getOptionalConfigArray('home.topVisits.filterBy') ?? [],
);
return await visitsApi
.list({
limit: numVisitsTotal ?? 8,