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,