Added support for filter using config instead of props
Signed-off-by: shmaram <shaharshmaram@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -235,7 +235,6 @@ export type VisitedByTypeProps = {
|
||||
numVisitsTotal?: number;
|
||||
loading?: boolean;
|
||||
kind: VisitedByTypeKind;
|
||||
filterBy?: VisitsApiQueryParams['filterBy'];
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
Vendored
+68
@@ -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;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user