From 6aa6f66e72c6e42e90087cc746325d4f9074da98 Mon Sep 17 00:00:00 2001 From: shmaram Date: Tue, 5 Dec 2023 17:50:28 +0200 Subject: [PATCH] Added support for filter using config instead of props Signed-off-by: shmaram --- .changeset/wise-flies-laugh.md | 2 +- plugins/home/README.md | 28 ++++++++ plugins/home/api-report.md | 1 - plugins/home/config.d.ts | 68 +++++++++++++++++++ plugins/home/package.json | 4 +- plugins/home/src/api/config.ts | 60 ++++++++++++++++ .../VisitedByType/Content.test.tsx | 35 +++++++--- .../VisitedByType/Content.tsx | 14 ++-- 8 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 plugins/home/config.d.ts create mode 100644 plugins/home/src/api/config.ts diff --git a/.changeset/wise-flies-laugh.md b/.changeset/wise-flies-laugh.md index d3d2ba8af0..4b31b4e9ba 100644 --- a/.changeset/wise-flies-laugh.md +++ b/.changeset/wise-flies-laugh.md @@ -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 diff --git a/plugins/home/README.md b/plugins/home/README.md index d41ae57162..6d75bcac10 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -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 diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 53baf80a0a..18049cae84 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -235,7 +235,6 @@ export type VisitedByTypeProps = { numVisitsTotal?: number; loading?: boolean; kind: VisitedByTypeKind; - filterBy?: VisitsApiQueryParams['filterBy']; }; // @public diff --git a/plugins/home/config.d.ts b/plugins/home/config.d.ts new file mode 100644 index 0000000000..3834a14d89 --- /dev/null +++ b/plugins/home/config.d.ts @@ -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; + }>; + }; + }; +} diff --git a/plugins/home/package.json b/plugins/home/package.json index 78eaf80fe3..e239332412 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -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" ] } diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts new file mode 100644 index 0000000000..893935f0e5 --- /dev/null +++ b/plugins/home/src/api/config.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); +} diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index 8dde5b0e1c..f45efc8781 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -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('', () => { }); it('allows items to be filtered', async () => { + const configApiMock = new MockConfigApi({ + home: { + topVisits: { + filterBy: [ + { + field: 'pathname', + operator: '==', + value: '/explore', + }, + ], + }, + }, + }); + const { getByText, queryByText } = await renderInTestApp( - + - + , ); diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx index 334f8c0e70..6fdc313c63 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx @@ -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,