From 384c1323824976f5c18708824953aa57e0071aab Mon Sep 17 00:00:00 2001 From: shmaram Date: Wed, 22 Nov 2023 10:38:31 +0200 Subject: [PATCH 01/15] support filter in HomePageVisitedByType Signed-off-by: shmaram --- .changeset/wise-flies-laugh.md | 5 +++ .../VisitedByType/Content.test.tsx | 19 ++++++++++ .../VisitedByType/Content.tsx | 38 +++++++++++++++---- 3 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 .changeset/wise-flies-laugh.md diff --git a/.changeset/wise-flies-laugh.md b/.changeset/wise-flies-laugh.md new file mode 100644 index 0000000000..79a9c3bf5a --- /dev/null +++ b/.changeset/wise-flies-laugh.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': minor +--- + +Added filter support for HomePageVisitedByType in order to enable filtering entites from the list diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index 4442c319e6..87b5b672a3 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -117,6 +117,25 @@ describe('', () => { expect(container.querySelectorAll('li')[0]).toBeVisible(); expect(container.querySelectorAll('li')[1]).not.toBeVisible(); }); + + it('allows items to be filtered', async () => { + const { getByText, queryByText } = await renderInTestApp( + + + + + , + ); + await waitFor(() => + expect(getByText('Explore Backstage')).toBeInTheDocument(), + ); + await waitFor(() => expect(queryByText('Tech Radar')).toBeNull()); + }); }); describe('', () => { diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx index 4e847b717f..400cb48e09 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx @@ -31,6 +31,11 @@ export type VisitedByTypeProps = { numVisitsTotal?: number; loading?: boolean; kind: VisitedByTypeKind; + filterBy?: Array<{ + field: keyof Visit; + operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + value: string | number; + }>; }; /** @@ -43,6 +48,7 @@ export const Content = ({ numVisitsTotal, loading, kind, + filterBy, }: VisitedByTypeProps) => { const { setContext, setVisits, setLoading } = useContext(); // Allows behavior override from properties @@ -65,18 +71,34 @@ export const Content = ({ const { loading: reqLoading } = useAsync(async () => { if (!visits && !loading && kind === 'recent') { return await visitsApi - .list({ - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'timestamp', direction: 'desc' }], - }) + .list( + filterBy + ? { + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'timestamp', direction: 'desc' }], + filterBy, + } + : { + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'timestamp', direction: 'desc' }], + }, + ) .then(setVisits); } if (!visits && !loading && kind === 'top') { return await visitsApi - .list({ - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'hits', direction: 'desc' }], - }) + .list( + filterBy + ? { + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'hits', direction: 'desc' }], + filterBy, + } + : { + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'hits', direction: 'desc' }], + }, + ) .then(setVisits); } return undefined; From 8e1a0aa867a30c9d89a09ff03defc8d5e1c2d988 Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 24 Nov 2023 00:45:33 +0200 Subject: [PATCH 02/15] Pull request changes Signed-off-by: shmaram --- .changeset/wise-flies-laugh.md | 2 +- plugins/home/api-report.md | 1 + .../VisitedByType/Content.test.tsx | 8 ++-- .../VisitedByType/Content.tsx | 42 ++++++------------- 4 files changed, 18 insertions(+), 35 deletions(-) diff --git a/.changeset/wise-flies-laugh.md b/.changeset/wise-flies-laugh.md index 79a9c3bf5a..d3d2ba8af0 100644 --- a/.changeset/wise-flies-laugh.md +++ b/.changeset/wise-flies-laugh.md @@ -2,4 +2,4 @@ '@backstage/plugin-home': minor --- -Added filter support for HomePageVisitedByType in order to enable filtering entites from the list +Added filter support for HomePageVisitedByType in order to enable filtering entities from the list diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 18049cae84..53baf80a0a 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -235,6 +235,7 @@ export type VisitedByTypeProps = { numVisitsTotal?: number; loading?: boolean; kind: VisitedByTypeKind; + filterBy?: VisitsApiQueryParams['filterBy']; }; // @public diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index 87b5b672a3..8dde5b0e1c 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -131,10 +131,10 @@ describe('', () => { , ); - await waitFor(() => - expect(getByText('Explore Backstage')).toBeInTheDocument(), - ); - await waitFor(() => expect(queryByText('Tech Radar')).toBeNull()); + await waitFor(() => { + expect(getByText('Explore Backstage')).toBeInTheDocument(); + expect(queryByText('Tech Radar')).toBeNull(); + }); }); }); diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx index 400cb48e09..334f8c0e70 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx @@ -16,7 +16,7 @@ import React, { useEffect } from 'react'; import { VisitedByType } from './VisitedByType'; -import { Visit, visitsApiRef } from '../../api/VisitsApi'; +import { Visit, VisitsApiQueryParams, visitsApiRef } from '../../api'; import { ContextValueOnly, useContext } from './Context'; import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/lib/useAsync'; @@ -31,11 +31,7 @@ export type VisitedByTypeProps = { numVisitsTotal?: number; loading?: boolean; kind: VisitedByTypeKind; - filterBy?: Array<{ - field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; - value: string | number; - }>; + filterBy?: VisitsApiQueryParams['filterBy']; }; /** @@ -71,34 +67,20 @@ export const Content = ({ const { loading: reqLoading } = useAsync(async () => { if (!visits && !loading && kind === 'recent') { return await visitsApi - .list( - filterBy - ? { - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'timestamp', direction: 'desc' }], - filterBy, - } - : { - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'timestamp', direction: 'desc' }], - }, - ) + .list({ + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'timestamp', direction: 'desc' }], + ...(filterBy && { filterBy }), + }) .then(setVisits); } if (!visits && !loading && kind === 'top') { return await visitsApi - .list( - filterBy - ? { - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'hits', direction: 'desc' }], - filterBy, - } - : { - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'hits', direction: 'desc' }], - }, - ) + .list({ + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'hits', direction: 'desc' }], + ...(filterBy && { filterBy }), + }) .then(setVisits); } return undefined; From 6aa6f66e72c6e42e90087cc746325d4f9074da98 Mon Sep 17 00:00:00 2001 From: shmaram Date: Tue, 5 Dec 2023 17:50:28 +0200 Subject: [PATCH 03/15] 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, From 9f2a817e87fd9ef4fc41538325fde0be04fa5643 Mon Sep 17 00:00:00 2001 From: shmaram Date: Tue, 5 Dec 2023 18:05:41 +0200 Subject: [PATCH 04/15] Fix README.md Signed-off-by: shmaram --- plugins/home/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index 6d75bcac10..37c308da60 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -335,7 +335,7 @@ Filtering is done by using 3 parameters: - pathname: string; - hits: number; - timestamp: number; - - entityRef?: string; + - entityRef: string; - `operator` - can be one of the following `'<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'` - `value` - the value of the filter From 41167e39f9754e280d6e7522490ccfadffb54b3d Mon Sep 17 00:00:00 2001 From: shmaram Date: Tue, 5 Dec 2023 18:11:51 +0200 Subject: [PATCH 05/15] Fix README.md Signed-off-by: shmaram --- plugins/home/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index 37c308da60..9883190be4 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -330,12 +330,12 @@ 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; + - 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 From 6640340085bff5d27deffba874385958b1be93fb Mon Sep 17 00:00:00 2001 From: shmaram Date: Tue, 5 Dec 2023 18:46:44 +0200 Subject: [PATCH 06/15] Fix README.md Signed-off-by: shmaram --- plugins/home/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index 9883190be4..c84bae684f 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -330,12 +330,12 @@ 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 + - `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 From 42dcdfbfd52e2cf8e118ae5f37d151365c0dd60b Mon Sep 17 00:00:00 2001 From: shmaram Date: Wed, 6 Dec 2023 21:23:01 +0200 Subject: [PATCH 07/15] Fix tests Signed-off-by: shmaram --- .../VisitedByType/Content.test.tsx | 123 +++++++++++++++++- 1 file changed, 117 insertions(+), 6 deletions(-) diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index f45efc8781..c6c0e6d81a 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -34,6 +34,13 @@ const visits = [ hits: 35, timestamp: Date.now() - 86400_000, }, + { + id: 'tech-radar', + name: 'Tech Radar', + pathname: '/tech-radar', + hits: 40, + timestamp: Date.now() - 360_000, + }, ]; const mockVisitsApi = { @@ -123,22 +130,24 @@ describe('', () => { expect(container.querySelectorAll('li')[1]).not.toBeVisible(); }); - it('allows items to be filtered', async () => { + it('allows recent items to be filtered using config', async () => { const configApiMock = new MockConfigApi({ home: { - topVisits: { + recentVisits: { filterBy: [ { field: 'pathname', operator: '==', - value: '/explore', + value: '/tech-radar', }, ], }, }, }); - const { getByText, queryByText } = await renderInTestApp( + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( ', () => { , ); await waitFor(() => { - expect(getByText('Explore Backstage')).toBeInTheDocument(); - expect(queryByText('Tech Radar')).toBeNull(); + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'timestamp', + }, + ], + filterBy: [{ field: 'pathname', operator: '==', value: '/tech-radar' }], + }); + }); + }); + + it('shows all recent items when there is no filtering in the config', async () => { + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'timestamp', + }, + ], + filterBy: [], + }); }); }); }); @@ -171,4 +213,73 @@ describe('', () => { expect(getByText('Explore Backstage')).toBeInTheDocument(), ); }); + + it('allows top items to be filtered using config', async () => { + const configApiMock = new MockConfigApi({ + home: { + topVisits: { + filterBy: [ + { + field: 'pathname', + operator: '==', + value: '/explore', + }, + ], + }, + }, + }); + + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'hits', + }, + ], + filterBy: [{ field: 'pathname', operator: '==', value: '/explore' }], + }); + }); + }); + + it('shows all top items when there is no filtering in the config', async () => { + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'hits', + }, + ], + filterBy: [], + }); + }); + }); }); From 5812f27ad66a5b17015960320b45ef04194e9829 Mon Sep 17 00:00:00 2001 From: shmaram Date: Thu, 21 Dec 2023 11:55:10 +0200 Subject: [PATCH 08/15] Fix tests Signed-off-by: shmaram --- plugins/home/src/api/config.test.ts | 84 +++++++++++++++++++++++++++++ plugins/home/src/api/config.ts | 9 +++- 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 plugins/home/src/api/config.test.ts diff --git a/plugins/home/src/api/config.test.ts b/plugins/home/src/api/config.test.ts new file mode 100644 index 0000000000..62ca09dd82 --- /dev/null +++ b/plugins/home/src/api/config.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { readFilterByConfig, readFilterByConfigs } from './config'; +import { MockConfigApi } from '@backstage/test-utils'; + +describe('config', () => { + describe('readFilterByConfig', () => { + it('returns filter data', async () => { + const mockConfig = new MockConfigApi( + { + field: 'pathname', + operator: '==', + value: '3' + }); + const res = readFilterByConfig(mockConfig) + expect(res).toEqual({ + field: 'pathname', + operator: '==', + value: '3' + }) + }); + + it('throws an error for invalid filter', async () => { + const mockConfig = new MockConfigApi( + { + myField: 'pathname', + operator: '==', + value: '3' + }); + expect(() => readFilterByConfig(mockConfig)).toThrow('Invalid config, Error: Missing required config value at \'field\'') + }); + }); + + describe('readFilterByConfigs', () => { + it('returns filter data', async () => { + const mockConfig1 = new MockConfigApi( + { + field: 'id', + operator: '==', + value: '3' + }); + const mockConfig2 = new MockConfigApi( + { + field: 'pathname', + operator: '==', + value: 'path' + }); + const res = readFilterByConfigs([mockConfig1, mockConfig2]) + expect(res).toEqual([{"field": "id", "operator": "==", "value": "3"}, {"field": "pathname", "operator": "==", "value": "path"}]) + }); + + it('return undefined for invalid filter', async () => { + const mockConfig1 = new MockConfigApi( + { + field: 'id', + operator: '==', + value: '3' + }); + const mockConfig2 = new MockConfigApi( + { + myField: 'pathname', + operator: '==', + value: 'path' + }); + const res = readFilterByConfigs([mockConfig1, mockConfig2]) + expect(res).toEqual(undefined) + }); + }); +}); + diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index 893935f0e5..6604084244 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -55,6 +55,11 @@ export function readFilterByConfig(config: Config): { */ export function readFilterByConfigs( configs: Config[], -): VisitsApiQueryParams['filterBy'] { - return configs.map(readFilterByConfig); +): VisitsApiQueryParams['filterBy'] | undefined { + try { + return configs.map(readFilterByConfig); + } + catch { + return undefined; + } } From 891fcd5482f57e93da64b58654a74989c219b7cf Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 22 Dec 2023 14:02:26 +0200 Subject: [PATCH 09/15] Fix tests Signed-off-by: shmaram --- plugins/home/src/api/VisitsStorageApi.test.ts | 2 +- .../VisitedByType/Content.test.tsx | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/plugins/home/src/api/VisitsStorageApi.test.ts b/plugins/home/src/api/VisitsStorageApi.test.ts index 6faeb62350..e361c624b9 100644 --- a/plugins/home/src/api/VisitsStorageApi.test.ts +++ b/plugins/home/src/api/VisitsStorageApi.test.ts @@ -250,7 +250,7 @@ describe('VisitsStorageApi.create', () => { ]); }); - it('filters by timestamp with >', async () => { + it('filters by timestamp with value >', async () => { const visits = await api.list({ filterBy: [{ field: 'timestamp', operator: '>', value: baseDate }], }); diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index c6c0e6d81a..1234291f1a 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -197,6 +197,47 @@ describe('', () => { }); }); }); + + it('allows recent items to have no filter if the filter config is not valid', async () => { + const configApiMock = new MockConfigApi({ + home: { + recentVisits: { + filterBy: [ + { + operator: '==', + value: '/tech-radar', + }, + ], + }, + }, + }); + + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( + + + + + , + ); + await waitFor(() => { + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'timestamp', + }, + ], + }); + }); + }); }); describe('', () => { From 27cb093b3c71fe276e4352d0a5c5bfb78a5c1dc3 Mon Sep 17 00:00:00 2001 From: shmaram Date: Thu, 25 Jan 2024 08:22:21 +0200 Subject: [PATCH 10/15] Rename config functions + update README.md Signed-off-by: shmaram --- plugins/home/README.md | 4 + plugins/home/src/api/config.test.ts | 100 +++++++++--------- plugins/home/src/api/config.ts | 9 +- .../VisitedByType/Content.tsx | 6 +- 4 files changed, 63 insertions(+), 56 deletions(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index c84bae684f..fdc5ce833b 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -353,6 +353,10 @@ home: value: ``` +Filters that are not defined according to the docs will be ignored. + +In order to validate the config you can use `backstage/cli config:check` + ## Contributing ### Homepage Components diff --git a/plugins/home/src/api/config.test.ts b/plugins/home/src/api/config.test.ts index 62ca09dd82..c2b715ec08 100644 --- a/plugins/home/src/api/config.test.ts +++ b/plugins/home/src/api/config.test.ts @@ -14,71 +14,75 @@ * limitations under the License. */ -import { readFilterByConfig, readFilterByConfigs } from './config'; +import { readFilterConfig, createFilterByQueryParamFromConfig } from './config'; import { MockConfigApi } from '@backstage/test-utils'; describe('config', () => { - describe('readFilterByConfig', () => { + describe('readFilterConfig', () => { it('returns filter data', async () => { - const mockConfig = new MockConfigApi( - { - field: 'pathname', - operator: '==', - value: '3' - }); - const res = readFilterByConfig(mockConfig) + const mockConfig = new MockConfigApi({ + field: 'pathname', + operator: '==', + value: '3', + }); + const res = readFilterConfig(mockConfig); expect(res).toEqual({ field: 'pathname', operator: '==', - value: '3' - }) + value: '3', + }); }); it('throws an error for invalid filter', async () => { - const mockConfig = new MockConfigApi( - { - myField: 'pathname', - operator: '==', - value: '3' - }); - expect(() => readFilterByConfig(mockConfig)).toThrow('Invalid config, Error: Missing required config value at \'field\'') + const mockConfig = new MockConfigApi({ + myField: 'pathname', + operator: '==', + value: '3', + }); + expect(() => readFilterConfig(mockConfig)).toThrow( + "Invalid config, Error: Missing required config value at 'field'", + ); }); }); - describe('readFilterByConfigs', () => { + describe('createFilterByQueryParamFromConfig', () => { it('returns filter data', async () => { - const mockConfig1 = new MockConfigApi( - { - field: 'id', - operator: '==', - value: '3' - }); - const mockConfig2 = new MockConfigApi( - { - field: 'pathname', - operator: '==', - value: 'path' - }); - const res = readFilterByConfigs([mockConfig1, mockConfig2]) - expect(res).toEqual([{"field": "id", "operator": "==", "value": "3"}, {"field": "pathname", "operator": "==", "value": "path"}]) + const mockConfig1 = new MockConfigApi({ + field: 'id', + operator: '==', + value: '3', + }); + const mockConfig2 = new MockConfigApi({ + field: 'pathname', + operator: '==', + value: 'path', + }); + const res = createFilterByQueryParamFromConfig([ + mockConfig1, + mockConfig2, + ]); + expect(res).toEqual([ + { field: 'id', operator: '==', value: '3' }, + { field: 'pathname', operator: '==', value: 'path' }, + ]); }); it('return undefined for invalid filter', async () => { - const mockConfig1 = new MockConfigApi( - { - field: 'id', - operator: '==', - value: '3' - }); - const mockConfig2 = new MockConfigApi( - { - myField: 'pathname', - operator: '==', - value: 'path' - }); - const res = readFilterByConfigs([mockConfig1, mockConfig2]) - expect(res).toEqual(undefined) + const mockConfig1 = new MockConfigApi({ + field: 'id', + operator: '==', + value: '3', + }); + const mockConfig2 = new MockConfigApi({ + myField: 'pathname', + operator: '==', + value: 'path', + }); + const res = createFilterByQueryParamFromConfig([ + mockConfig1, + mockConfig2, + ]); + expect(res).toEqual(undefined); }); }); }); - diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index 6604084244..a8af29fc9e 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -24,7 +24,7 @@ import { Config } from '@backstage/config'; * * @public */ -export function readFilterByConfig(config: Config): { +export function readFilterConfig(config: Config): { field: keyof Visit; operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; value: string | number; @@ -53,13 +53,12 @@ export function readFilterByConfig(config: Config): { * * @public */ -export function readFilterByConfigs( +export function createFilterByQueryParamFromConfig( configs: Config[], ): VisitsApiQueryParams['filterBy'] | undefined { try { - return configs.map(readFilterByConfig); - } - catch { + return configs.map(readFilterConfig); + } catch { return undefined; } } diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx index 6fdc313c63..6c9d889d72 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx @@ -15,7 +15,7 @@ */ import React, { useEffect } from 'react'; -import { readFilterByConfigs } from '../../api/config'; +import { createFilterByQueryParamFromConfig } from '../../api/config'; import { VisitedByType } from './VisitedByType'; import { Visit, visitsApiRef } from '../../api'; import { ContextValueOnly, useContext } from './Context'; @@ -66,7 +66,7 @@ export const Content = ({ const visitsApi = useApi(visitsApiRef); const { loading: reqLoading } = useAsync(async () => { if (!visits && !loading && kind === 'recent') { - const filterBy = readFilterByConfigs( + const filterBy = createFilterByQueryParamFromConfig( config.getOptionalConfigArray('home.recentVisits.filterBy') ?? [], ); return await visitsApi @@ -78,7 +78,7 @@ export const Content = ({ .then(setVisits); } if (!visits && !loading && kind === 'top') { - const filterBy = readFilterByConfigs( + const filterBy = createFilterByQueryParamFromConfig( config.getOptionalConfigArray('home.topVisits.filterBy') ?? [], ); return await visitsApi From 7944df5ae449854603bffd83f35e65b993bcd68e Mon Sep 17 00:00:00 2001 From: shmaram Date: Thu, 25 Jan 2024 10:04:48 +0200 Subject: [PATCH 11/15] update package.json Signed-off-by: shmaram --- plugins/home/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/home/package.json b/plugins/home/package.json index 603b286c7a..e8fa2b666c 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -50,6 +50,7 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", diff --git a/yarn.lock b/yarn.lock index 0e3d81bc7f..458717f31a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7021,6 +7021,7 @@ __metadata: "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" From c57d61f0a9c2754bcb019fc8d555a14c5c88ab99 Mon Sep 17 00:00:00 2001 From: shmaram Date: Thu, 25 Jan 2024 23:10:30 +0200 Subject: [PATCH 12/15] Return undefined for invalid filters + fix README.md Signed-off-by: shmaram --- plugins/home/README.md | 2 +- plugins/home/src/api/config.test.ts | 21 ++++++------ plugins/home/src/api/config.ts | 19 +++++++---- .../VisitedByType/Content.test.tsx | 32 ++++++++++++++++++- 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index fdc5ce833b..066e637725 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -353,7 +353,7 @@ home: value: ``` -Filters that are not defined according to the docs will be ignored. +`filterBy` configs that are not defined in the above format will be ignored. In order to validate the config you can use `backstage/cli config:check` diff --git a/plugins/home/src/api/config.test.ts b/plugins/home/src/api/config.test.ts index c2b715ec08..178287bb3d 100644 --- a/plugins/home/src/api/config.test.ts +++ b/plugins/home/src/api/config.test.ts @@ -33,15 +33,14 @@ describe('config', () => { }); }); - it('throws an error for invalid filter', async () => { - const mockConfig = new MockConfigApi({ + it('returns undefined for invalid filter', async () => { + const mockInvalidConfig = new MockConfigApi({ myField: 'pathname', operator: '==', value: '3', }); - expect(() => readFilterConfig(mockConfig)).toThrow( - "Invalid config, Error: Missing required config value at 'field'", - ); + const res = readFilterConfig(mockInvalidConfig); + expect(res).toEqual(undefined); }); }); @@ -67,22 +66,22 @@ describe('config', () => { ]); }); - it('return undefined for invalid filter', async () => { - const mockConfig1 = new MockConfigApi({ + it('returns only invalid filters', async () => { + const mockValidConfig = new MockConfigApi({ field: 'id', operator: '==', value: '3', }); - const mockConfig2 = new MockConfigApi({ + const mockInvalidConfig = new MockConfigApi({ myField: 'pathname', operator: '==', value: 'path', }); const res = createFilterByQueryParamFromConfig([ - mockConfig1, - mockConfig2, + mockValidConfig, + mockInvalidConfig, ]); - expect(res).toEqual(undefined); + expect(res).toEqual([{ field: 'id', operator: '==', value: '3' }]); }); }); }); diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index a8af29fc9e..3cc6d93f24 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -24,11 +24,13 @@ import { Config } from '@backstage/config'; * * @public */ -export function readFilterConfig(config: Config): { - field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; - value: string | number; -} { +export function readFilterConfig(config: Config): + | { + field: keyof Visit; + operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + value: string | number; + } + | undefined { try { const field = config.getString('field') as keyof Visit; const operator = config.getString('operator') as @@ -42,7 +44,8 @@ export function readFilterConfig(config: Config): { const value = config.getString('value'); return { field, operator, value }; } catch (error) { - throw new Error(`Invalid config, ${error}`); + // invalid filter config - ignore filter + return undefined; } } @@ -57,7 +60,9 @@ export function createFilterByQueryParamFromConfig( configs: Config[], ): VisitsApiQueryParams['filterBy'] | undefined { try { - return configs.map(readFilterConfig); + return configs + .map(readFilterConfig) + .filter(Boolean) as VisitsApiQueryParams['filterBy']; } catch { return undefined; } diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index 1234291f1a..2a7a6481ba 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -43,12 +43,21 @@ const visits = [ }, ]; -const mockVisitsApi = { +let mockVisitsApi = { save: async () => visits[0], list: async () => visits, }; describe('', () => { + beforeEach(() => { + mockVisitsApi = { + save: async () => visits[0], + list: async () => visits, + }; + }); + + afterEach(() => jest.resetAllMocks()); + it('renders', async () => { const { getByText } = await renderInTestApp( @@ -207,6 +216,11 @@ describe('', () => { operator: '==', value: '/tech-radar', }, + { + field: 'pathname', + operator: '==', + value: '/explore', + }, ], }, }, @@ -235,12 +249,28 @@ describe('', () => { field: 'timestamp', }, ], + filterBy: [ + { + field: 'pathname', + operator: '==', + value: '/explore', + }, + ], }); }); }); }); describe('', () => { + beforeEach(() => { + mockVisitsApi = { + save: async () => visits[0], + list: async () => visits, + }; + }); + + afterEach(() => jest.resetAllMocks()); + it('renders', async () => { const { getByText } = await renderInTestApp( From 20843193e8908f76c3f68c9eadb996c1a0c03f01 Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 26 Jan 2024 11:44:17 +0200 Subject: [PATCH 13/15] Created Operators type and added type guard + fixed filter value to be string or number Signed-off-by: shmaram --- plugins/home/src/api/VisitsApi.ts | 16 +++++++++++++++- plugins/home/src/api/config.ts | 27 +++++++++++++++------------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/plugins/home/src/api/VisitsApi.ts b/plugins/home/src/api/VisitsApi.ts index ad750d81f2..610f4be483 100644 --- a/plugins/home/src/api/VisitsApi.ts +++ b/plugins/home/src/api/VisitsApi.ts @@ -16,6 +16,20 @@ import { createApiRef } from '@backstage/core-plugin-api'; +/** + * @public + * The operators that can be used in filter. + */ +export type Operators = '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + +/** + * @public + * Type guard for operators. + */ +export const isOperator = (s: string): s is Operators => { + return ['<', '<=', '==', '!=', '>', '>=', 'contains'].includes(s); +}; + /** * @public * Model for a visit entity. @@ -84,7 +98,7 @@ export type VisitsApiQueryParams = { */ filterBy?: Array<{ field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + operator: Operators; value: string | number; }>; }; diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index 3cc6d93f24..359026958f 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { Visit, VisitsApiQueryParams } from './VisitsApi'; +import { + Visit, + VisitsApiQueryParams, + Operators, + isOperator, +} from './VisitsApi'; import { Config } from '@backstage/config'; /** @@ -24,25 +29,23 @@ import { Config } from '@backstage/config'; * * @public */ + export function readFilterConfig(config: Config): | { field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + operator: Operators; value: string | number; } | undefined { 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 }; + const operator = config.getString('operator'); + const value = + config.getOptionalNumber('value') ?? config.getString('value'); + if (isOperator(operator)) { + return { field, operator, value }; + } + return undefined; } catch (error) { // invalid filter config - ignore filter return undefined; From c47460ba98257317099bec75eaa837ac94608619 Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 26 Jan 2024 12:46:03 +0200 Subject: [PATCH 14/15] fix tests + support value to be number or string Signed-off-by: shmaram --- plugins/home/src/api/config.test.ts | 10 +++++----- plugins/home/src/api/config.ts | 19 ++++++++++++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/plugins/home/src/api/config.test.ts b/plugins/home/src/api/config.test.ts index 178287bb3d..c21d856038 100644 --- a/plugins/home/src/api/config.test.ts +++ b/plugins/home/src/api/config.test.ts @@ -23,13 +23,13 @@ describe('config', () => { const mockConfig = new MockConfigApi({ field: 'pathname', operator: '==', - value: '3', + value: '/home', }); const res = readFilterConfig(mockConfig); expect(res).toEqual({ field: 'pathname', operator: '==', - value: '3', + value: '/home', }); }); @@ -66,11 +66,11 @@ describe('config', () => { ]); }); - it('returns only invalid filters', async () => { + it('returns only valid filters', async () => { const mockValidConfig = new MockConfigApi({ field: 'id', operator: '==', - value: '3', + value: 3, }); const mockInvalidConfig = new MockConfigApi({ myField: 'pathname', @@ -81,7 +81,7 @@ describe('config', () => { mockValidConfig, mockInvalidConfig, ]); - expect(res).toEqual([{ field: 'id', operator: '==', value: '3' }]); + expect(res).toEqual([{ field: 'id', operator: '==', value: 3 }]); }); }); }); diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index 359026958f..65833c13d5 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -40,9 +40,8 @@ export function readFilterConfig(config: Config): try { const field = config.getString('field') as keyof Visit; const operator = config.getString('operator'); - const value = - config.getOptionalNumber('value') ?? config.getString('value'); - if (isOperator(operator)) { + const value = getValue(config); + if (isOperator(operator) && value !== undefined) { return { field, operator, value }; } return undefined; @@ -52,6 +51,20 @@ export function readFilterConfig(config: Config): } } +function getValue(config: Config) { + let value = undefined; + try { + value = config.getString('value'); + } catch (error) { + try { + value = config.getNumber('value'); + } catch { + // value is not string or number - ignore filter + } + } + return value; +} + /** * Reads a set of filter by configs. * From 877efd01ca005b7a419c0b73fd0e414040a0fcb9 Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 26 Jan 2024 12:59:55 +0200 Subject: [PATCH 15/15] update api-report.md Signed-off-by: shmaram --- plugins/home/api-report.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index dd6e22c57d..023c649ab4 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -169,6 +169,9 @@ export const homePlugin: BackstagePlugin< {} >; +// @public +export const isOperator: (s: string) => s is Operators; + // @public export type LayoutConfiguration = { component: ReactElement | string; @@ -181,6 +184,9 @@ export type LayoutConfiguration = { resizable?: boolean; }; +// @public +export type Operators = '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + // @public @deprecated (undocumented) export type RendererProps = RendererProps_2; @@ -271,7 +277,7 @@ export type VisitsApiQueryParams = { }>; filterBy?: Array<{ field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + operator: Operators; value: string | number; }>; };