feature(home-plugin): Rename component to VisitedByType
This renaming is to enable Top Visited and Recently Visited to be served by the same component. This is to reduce code duplication. Signed-off-by: Renan Mendes Carvalho <aitherios@gmail.com>
This commit is contained in:
committed by
Camila Belo
parent
46aa692057
commit
160761b049
+20
-34
@@ -15,6 +15,7 @@ import { ComponentParts as ComponentParts_2 } from '@backstage/plugin-home-react
|
||||
import { ComponentRenderer as ComponentRenderer_2 } from '@backstage/plugin-home-react';
|
||||
import { createCardExtension as createCardExtension_2 } from '@backstage/plugin-home-react';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
@@ -50,7 +51,7 @@ export const ComponentAccordion: (props: {
|
||||
Actions?: (() => JSX.Element) | undefined;
|
||||
Settings?: (() => JSX.Element) | undefined;
|
||||
ContextProvider?: ((props: any) => JSX.Element) | undefined;
|
||||
}) => JSX.Element;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type ComponentParts = ComponentParts_2;
|
||||
@@ -63,7 +64,7 @@ export const ComponentTab: (props: {
|
||||
title: string;
|
||||
Content: () => JSX.Element;
|
||||
ContextProvider?: ((props: any) => JSX.Element) | undefined;
|
||||
}) => JSX.Element;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ComponentTabs: (props: {
|
||||
@@ -72,7 +73,7 @@ export const ComponentTabs: (props: {
|
||||
label: string;
|
||||
Component: () => JSX.Element;
|
||||
}[];
|
||||
}) => JSX.Element;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const createCardExtension: typeof createCardExtension_2;
|
||||
@@ -80,7 +81,7 @@ export const createCardExtension: typeof createCardExtension_2;
|
||||
// @public
|
||||
export const CustomHomepageGrid: (
|
||||
props: CustomHomepageGridProps,
|
||||
) => JSX.Element;
|
||||
) => React_2.JSX.Element;
|
||||
|
||||
// @public
|
||||
export type CustomHomepageGridProps = {
|
||||
@@ -102,41 +103,41 @@ export type CustomHomepageGridProps = {
|
||||
export const HeaderWorldClock: (props: {
|
||||
clockConfigs: ClockConfig[];
|
||||
customTimeFormat?: Intl.DateTimeFormatOptions | undefined;
|
||||
}) => JSX.Element | null;
|
||||
}) => JSX_2.Element | null;
|
||||
|
||||
// @public
|
||||
export const HomePageCompanyLogo: (props: {
|
||||
logo?: ReactNode;
|
||||
className?: string | undefined;
|
||||
}) => JSX.Element;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const HomepageCompositionRoot: (props: {
|
||||
title?: string | undefined;
|
||||
children?: ReactNode;
|
||||
}) => JSX.Element;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const HomePageRandomJoke: (
|
||||
props: CardExtensionProps_2<{
|
||||
defaultCategory?: 'any' | 'programming' | undefined;
|
||||
}>,
|
||||
) => JSX.Element;
|
||||
) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export const HomePageStarredEntities: (
|
||||
props: CardExtensionProps_2<unknown>,
|
||||
) => JSX.Element;
|
||||
) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export const HomePageToolkit: (
|
||||
props: CardExtensionProps_2<ToolkitContentProps>,
|
||||
) => JSX.Element;
|
||||
) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export const HomePageVisitedByType: (
|
||||
props: CardExtensionProps_2<VisitedByTypeProps>,
|
||||
) => JSX.Element;
|
||||
) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const homePlugin: BackstagePlugin<
|
||||
@@ -167,7 +168,7 @@ export const SettingsModal: (props: {
|
||||
close: Function;
|
||||
componentName?: string | undefined;
|
||||
children: JSX.Element;
|
||||
}) => JSX.Element;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TemplateBackstageLogo: (props: {
|
||||
@@ -175,10 +176,10 @@ export const TemplateBackstageLogo: (props: {
|
||||
svg: string;
|
||||
path: string;
|
||||
};
|
||||
}) => JSX.Element;
|
||||
}) => React_2.JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TemplateBackstageLogoIcon: () => JSX.Element;
|
||||
export const TemplateBackstageLogoIcon: () => React_2.JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type Tool = {
|
||||
@@ -192,8 +193,6 @@ export type ToolkitContentProps = {
|
||||
tools: Tool[];
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "Visit" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export type Visit = {
|
||||
id: string;
|
||||
@@ -204,8 +203,9 @@ export type Visit = {
|
||||
entityRef?: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "VisitedByTypeProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type VisitedByTypeKind = 'recent' | 'top';
|
||||
|
||||
// @public (undocumented)
|
||||
export type VisitedByTypeProps = {
|
||||
visits?: Array<Visit>;
|
||||
@@ -215,8 +215,6 @@ export type VisitedByTypeProps = {
|
||||
kind: VisitedByTypeKind;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "VisitFilter" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type VisitFilter = {
|
||||
field: string;
|
||||
@@ -224,18 +222,12 @@ export type VisitFilter = {
|
||||
value: JsonValue;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "VisitsApi" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export interface VisitsApi {
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
listUserVisits(queryParams: VisitsApiQueryParams): Promise<Visit[]>;
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
listUserVisits(queryParams?: VisitsApiQueryParams): Promise<Visit[]>;
|
||||
saveVisit(pageVisit: Omit<Visit, 'id' | 'hits' | 'timestamp'>): Promise<void>;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "VisitsApiQueryParams" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export type VisitsApiQueryParams = {
|
||||
limit?: number;
|
||||
@@ -243,22 +235,16 @@ export type VisitsApiQueryParams = {
|
||||
filterBy?: VisitFilter[];
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "visitsApiRef" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const visitsApiRef: ApiRef<VisitsApi>;
|
||||
|
||||
// @public
|
||||
export const WelcomeTitle: ({
|
||||
language,
|
||||
}: WelcomeTitleLanguageProps) => JSX.Element;
|
||||
}: WelcomeTitleLanguageProps) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type WelcomeTitleLanguageProps = {
|
||||
language?: string[];
|
||||
};
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/homePageComponents/VisitedByType/Content.d.ts:9:5 - (ae-forgotten-export) The symbol "VisitedByTypeKind" needs to be exported by the entry point index.d.ts
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@ import { createApiRef } from '@backstage/core-plugin-api';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
|
||||
/**
|
||||
@experimental
|
||||
* @public
|
||||
* Model for a visit entity.
|
||||
*/
|
||||
export type Visit = {
|
||||
@@ -48,6 +48,7 @@ export type Visit = {
|
||||
entityRef?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type VisitFilter = {
|
||||
field: string;
|
||||
operator: '<' | '<=' | '==' | '>' | '>=' | 'contains';
|
||||
@@ -55,7 +56,7 @@ export type VisitFilter = {
|
||||
};
|
||||
|
||||
/**
|
||||
@experimental
|
||||
* @public
|
||||
* This data structure represents the parameters associated with search queries for visits.
|
||||
*/
|
||||
export type VisitsApiQueryParams = {
|
||||
@@ -85,22 +86,23 @@ export type VisitsApiQueryParams = {
|
||||
};
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
* @public
|
||||
* Visits API public contract.
|
||||
*/
|
||||
export interface VisitsApi {
|
||||
/**
|
||||
* Persist a new visit.
|
||||
* @param pageVisit | a new visit data.
|
||||
* @param pageVisit - a new visit data
|
||||
*/
|
||||
saveVisit(pageVisit: Omit<Visit, 'id' | 'hits' | 'timestamp'>): Promise<void>;
|
||||
/**
|
||||
* Get the logged user visits.
|
||||
* @param queryParams | optional search query params.
|
||||
* @param queryParams - optional search query params.
|
||||
*/
|
||||
listUserVisits(queryParams: VisitsApiQueryParams): Promise<Visit[]>;
|
||||
listUserVisits(queryParams?: VisitsApiQueryParams): Promise<Visit[]>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const visitsApiRef = createApiRef<VisitsApi>({
|
||||
id: 'homepage.visits',
|
||||
});
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { VisitList } from './VisitList';
|
||||
import { render } from '@testing-library/react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
describe('<VisitList/>', () => {
|
||||
it('renders with mandatory parameters', async () => {
|
||||
const { getByText } = await render(
|
||||
<VisitList title="My title" detailType="time-ago" />,
|
||||
);
|
||||
expect(getByText('My title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders skeleton when loading is true', async () => {
|
||||
const { container } = await render(
|
||||
<VisitList title="My title" detailType="time-ago" loading />,
|
||||
);
|
||||
expect(container.querySelectorAll('li')).toHaveLength(8);
|
||||
expect(container.querySelectorAll('.MuiSkeleton-root')).toHaveLength(16);
|
||||
});
|
||||
|
||||
it('renders specified amount of items', async () => {
|
||||
const { container } = await render(
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="time-ago"
|
||||
loading
|
||||
numVisitsOpen={1}
|
||||
numVisitsTotal={2}
|
||||
/>,
|
||||
);
|
||||
expect(container.querySelectorAll('li')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('renders some items hidden', async () => {
|
||||
const { container } = await render(
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="time-ago"
|
||||
loading
|
||||
numVisitsOpen={1}
|
||||
numVisitsTotal={2}
|
||||
/>,
|
||||
);
|
||||
expect(container.querySelectorAll('li')[0]).toBeVisible();
|
||||
expect(container.querySelectorAll('li')[1]).not.toBeVisible();
|
||||
});
|
||||
|
||||
it('renders all items when not collapsed', async () => {
|
||||
const { container } = await render(
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="time-ago"
|
||||
loading
|
||||
collapsed={false}
|
||||
numVisitsOpen={1}
|
||||
numVisitsTotal={2}
|
||||
/>,
|
||||
);
|
||||
expect(container.querySelectorAll('li')[0]).toBeVisible();
|
||||
expect(container.querySelectorAll('li')[1]).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders visit with time-ago', async () => {
|
||||
const { container, getByText } = await render(
|
||||
<BrowserRouter>
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="time-ago"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
,
|
||||
</BrowserRouter>,
|
||||
);
|
||||
expect(container.querySelectorAll('li')).toHaveLength(1);
|
||||
expect(getByText('Explore Backstage')).toBeInTheDocument();
|
||||
expect(getByText('1 day ago')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders visit with hits', async () => {
|
||||
const { container, getByText } = await render(
|
||||
<BrowserRouter>
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="hits"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
,
|
||||
</BrowserRouter>,
|
||||
);
|
||||
expect(container.querySelectorAll('li')).toHaveLength(1);
|
||||
expect(getByText('Explore Backstage')).toBeInTheDocument();
|
||||
expect(getByText('35 times')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders text warning about few items', async () => {
|
||||
const { getByText } = await render(
|
||||
<BrowserRouter>
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="hits"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
,
|
||||
</BrowserRouter>,
|
||||
);
|
||||
expect(
|
||||
getByText('The more pages you visit, the more pages will appear here.'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders text warning about no items', async () => {
|
||||
const { getByText } = await render(
|
||||
<BrowserRouter>
|
||||
<VisitList title="My title" detailType="hits" visits={[]} />,
|
||||
</BrowserRouter>,
|
||||
);
|
||||
expect(getByText('There are no visits to show yet.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -30,21 +30,21 @@ const useStyles = makeStyles(_theme => ({
|
||||
}));
|
||||
|
||||
export const VisitList = ({
|
||||
visits,
|
||||
title,
|
||||
detailType,
|
||||
visits = [],
|
||||
numVisitsOpen = 3,
|
||||
numVisitsTotal = 8,
|
||||
collapsed = true,
|
||||
loading = false,
|
||||
}: {
|
||||
visits: Array<Visit>;
|
||||
title: string;
|
||||
detailType: ItemDetailType;
|
||||
visits?: Visit[];
|
||||
numVisitsOpen?: number;
|
||||
numVisitsTotal?: number;
|
||||
collapsed: boolean;
|
||||
loading: boolean;
|
||||
collapsed?: boolean;
|
||||
loading?: boolean;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* 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 React, { createContext } from 'react';
|
||||
import { Visit } from '../../api/VisitsApi';
|
||||
|
||||
export type ContextValue = {
|
||||
collapsed: boolean;
|
||||
setCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
numVisitsOpen: number;
|
||||
setNumVisitsOpen: React.Dispatch<React.SetStateAction<number>>;
|
||||
numVisitsTotal: number;
|
||||
setNumVisitsTotal: React.Dispatch<React.SetStateAction<number>>;
|
||||
visits: Array<Visit>;
|
||||
setVisits: React.Dispatch<React.SetStateAction<Array<Visit>>>;
|
||||
loading: boolean;
|
||||
setLoading: React.Dispatch<boolean>;
|
||||
};
|
||||
|
||||
const defaultContextValue = {
|
||||
collapsed: true,
|
||||
setCollapsed: () => {},
|
||||
numVisitsOpen: 3,
|
||||
setNumVisitsOpen: () => {},
|
||||
numVisitsTotal: 8,
|
||||
setNumVisitsTotal: () => {},
|
||||
visits: [],
|
||||
setVisits: () => {},
|
||||
loading: true,
|
||||
setLoading: () => {},
|
||||
};
|
||||
|
||||
const Context = createContext<ContextValue>(defaultContextValue);
|
||||
|
||||
export const ContextProvider = ({ children }: { children: JSX.Element }) => {
|
||||
const [collapsed, setCollapsed] = React.useState(
|
||||
defaultContextValue.collapsed,
|
||||
);
|
||||
const [numVisitsOpen, setNumVisitsOpen] = React.useState(
|
||||
defaultContextValue.numVisitsOpen,
|
||||
);
|
||||
const [numVisitsTotal, setNumVisitsTotal] = React.useState(
|
||||
defaultContextValue.numVisitsTotal,
|
||||
);
|
||||
const [visits, setVisits] = React.useState<Array<Visit>>(
|
||||
defaultContextValue.visits,
|
||||
);
|
||||
const [loading, setLoading] = React.useState<boolean>(
|
||||
defaultContextValue.loading,
|
||||
);
|
||||
|
||||
const value: ContextValue = {
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
numVisitsOpen,
|
||||
setNumVisitsOpen,
|
||||
numVisitsTotal,
|
||||
setNumVisitsTotal,
|
||||
visits,
|
||||
setVisits,
|
||||
loading,
|
||||
setLoading,
|
||||
};
|
||||
|
||||
return <Context.Provider value={value}>{children}</Context.Provider>;
|
||||
};
|
||||
|
||||
export const useContext = () => {
|
||||
const value = React.useContext(Context);
|
||||
|
||||
if (value === undefined)
|
||||
throw new Error(
|
||||
'RecentlyVisited useContext found undefined ContextValue, <ContextProvider/> could be missing',
|
||||
);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export default Context;
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { RecentlyVisited } from './RecentlyVisited';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('<RecentlyVisited/>', () => {
|
||||
it('should render', async () => {
|
||||
const { getByText } = await renderInTestApp(<RecentlyVisited />);
|
||||
expect(getByText('RecentlyVisited')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { Content } from './Content';
|
||||
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
|
||||
import { visitsApiRef } from '../../api';
|
||||
import { ContextProvider } from './Context';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
||||
const visits = [
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
];
|
||||
|
||||
const mockVisitsApi = {
|
||||
saveVisit: async () => {},
|
||||
listUserVisits: async () => visits,
|
||||
};
|
||||
|
||||
describe('<Content kind="recent"/>', () => {
|
||||
it('renders', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<ContextProvider>
|
||||
<Content kind="recent" />
|
||||
</ContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
expect(getByText('Recently Visited')).toBeInTheDocument();
|
||||
await waitFor(() =>
|
||||
expect(getByText('Explore Backstage')).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it('allows visits to be overridden', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<ContextProvider>
|
||||
<Content
|
||||
kind="recent"
|
||||
visits={[
|
||||
{
|
||||
id: 'tech-radar',
|
||||
name: 'Tech Radar',
|
||||
pathname: '/tech-radar',
|
||||
hits: 40,
|
||||
timestamp: Date.now() - 360_000,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
expect(getByText('Recently Visited')).toBeInTheDocument();
|
||||
await waitFor(() => expect(getByText('Tech Radar')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('allows loading to be overridden', async () => {
|
||||
const { container } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<ContextProvider>
|
||||
<Content kind="recent" loading />
|
||||
</ContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
expect(container.querySelector('.MuiSkeleton-root')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('allows number of items to be specified', async () => {
|
||||
const { container } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<ContextProvider>
|
||||
<Content
|
||||
kind="recent"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
{
|
||||
id: 'tech-radar',
|
||||
name: 'Tech Radar',
|
||||
pathname: '/tech-radar',
|
||||
hits: 40,
|
||||
timestamp: Date.now() - 360_000,
|
||||
},
|
||||
]}
|
||||
numVisitsOpen={1}
|
||||
numVisitsTotal={2}
|
||||
/>
|
||||
</ContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
expect(container.querySelectorAll('li')).toHaveLength(2);
|
||||
expect(container.querySelectorAll('li')[0]).toBeVisible();
|
||||
expect(container.querySelectorAll('li')[1]).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
describe('<Content kind="top"/>', () => {
|
||||
it('renders', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<ContextProvider>
|
||||
<Content kind="top" />
|
||||
</ContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
expect(getByText('Top Visited')).toBeInTheDocument();
|
||||
await waitFor(() =>
|
||||
expect(getByText('Explore Backstage')).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
});
|
||||
+33
-24
@@ -15,17 +15,22 @@
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { RecentlyVisited } from './RecentlyVisited';
|
||||
import { VisitedByType } from './VisitedByType';
|
||||
import { Visit, visitsApiRef } from '../../api/VisitsApi';
|
||||
import { useContext } from './Context';
|
||||
import { ContextValueOnly, useContext } from './Context';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
export type RecentlyVisitedProps = {
|
||||
/** @public */
|
||||
export type VisitedByTypeKind = 'recent' | 'top';
|
||||
|
||||
/** @public */
|
||||
export type VisitedByTypeProps = {
|
||||
visits?: Array<Visit>;
|
||||
numVisitsOpen?: number;
|
||||
numVisitsTotal?: number;
|
||||
loading?: boolean;
|
||||
kind: VisitedByTypeKind;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -37,40 +42,44 @@ export const Content = ({
|
||||
numVisitsOpen,
|
||||
numVisitsTotal,
|
||||
loading,
|
||||
}: RecentlyVisitedProps) => {
|
||||
const { setVisits, setNumVisitsOpen, setNumVisitsTotal, setLoading } =
|
||||
useContext();
|
||||
kind,
|
||||
}: VisitedByTypeProps) => {
|
||||
const { setContext, setVisits, setLoading } = useContext();
|
||||
// Allows behavior override from properties
|
||||
useEffect(() => {
|
||||
const context: Partial<ContextValueOnly> = {};
|
||||
context.kind = kind;
|
||||
if (visits) {
|
||||
setVisits(visits);
|
||||
setLoading(false);
|
||||
context.visits = visits;
|
||||
context.loading = false;
|
||||
} else if (loading) {
|
||||
setLoading(loading);
|
||||
context.loading = loading;
|
||||
}
|
||||
if (numVisitsOpen) setNumVisitsOpen(numVisitsOpen);
|
||||
if (numVisitsTotal) setNumVisitsTotal(numVisitsTotal);
|
||||
}, [
|
||||
visits,
|
||||
numVisitsOpen,
|
||||
numVisitsTotal,
|
||||
loading,
|
||||
setVisits,
|
||||
setNumVisitsOpen,
|
||||
setNumVisitsTotal,
|
||||
setLoading,
|
||||
]);
|
||||
if (numVisitsOpen) context.numVisitsOpen = numVisitsOpen;
|
||||
if (numVisitsTotal) context.numVisitsTotal = numVisitsTotal;
|
||||
setContext(state => ({ ...state, ...context }));
|
||||
}, [setContext, kind, visits, loading, numVisitsOpen, numVisitsTotal]);
|
||||
|
||||
// Fetches data from visitsApi in case visits and loading are not provided
|
||||
const visitsApi = useApi(visitsApiRef);
|
||||
const { loading: reqLoading } = useAsync(async () => {
|
||||
if (!visits && !loading) {
|
||||
await visitsApi
|
||||
if (!visits && !loading && kind === 'recent') {
|
||||
return await visitsApi
|
||||
.listUserVisits({
|
||||
limit: numVisitsTotal ?? 8,
|
||||
orderBy: { timestamp: 'desc' },
|
||||
})
|
||||
.then(setVisits);
|
||||
}
|
||||
if (!visits && !loading && kind === 'top') {
|
||||
return await visitsApi
|
||||
.listUserVisits({
|
||||
limit: numVisitsTotal ?? 8,
|
||||
orderBy: { hits: 'desc' },
|
||||
})
|
||||
.then(setVisits);
|
||||
}
|
||||
return undefined;
|
||||
}, [visitsApi, visits, loading, setVisits]);
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
@@ -78,5 +87,5 @@ export const Content = ({
|
||||
}
|
||||
}, [loading, setLoading, reqLoading]);
|
||||
|
||||
return <RecentlyVisited />;
|
||||
return <VisitedByType />;
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 React, { Dispatch, SetStateAction, createContext, useMemo } from 'react';
|
||||
import { Visit } from '../../api/VisitsApi';
|
||||
import { VisitedByTypeKind } from './Content';
|
||||
|
||||
export type ContextValueOnly = {
|
||||
collapsed: boolean;
|
||||
numVisitsOpen: number;
|
||||
numVisitsTotal: number;
|
||||
visits: Array<Visit>;
|
||||
loading: boolean;
|
||||
kind: VisitedByTypeKind;
|
||||
};
|
||||
|
||||
export type ContextValue = ContextValueOnly & {
|
||||
setCollapsed: Dispatch<SetStateAction<boolean>>;
|
||||
setNumVisitsOpen: Dispatch<SetStateAction<number>>;
|
||||
setNumVisitsTotal: Dispatch<SetStateAction<number>>;
|
||||
setVisits: Dispatch<SetStateAction<Array<Visit>>>;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
setKind: Dispatch<SetStateAction<VisitedByTypeKind>>;
|
||||
setContext: Dispatch<SetStateAction<ContextValueOnly>>;
|
||||
};
|
||||
|
||||
const defaultContextValueOnly: ContextValueOnly = {
|
||||
collapsed: true,
|
||||
numVisitsOpen: 3,
|
||||
numVisitsTotal: 8,
|
||||
visits: [],
|
||||
loading: true,
|
||||
kind: 'recent',
|
||||
};
|
||||
|
||||
export const defaultContextValue: ContextValue = {
|
||||
...defaultContextValueOnly,
|
||||
setCollapsed: () => {},
|
||||
setNumVisitsOpen: () => {},
|
||||
setNumVisitsTotal: () => {},
|
||||
setVisits: () => {},
|
||||
setLoading: () => {},
|
||||
setKind: () => {},
|
||||
setContext: () => {},
|
||||
};
|
||||
|
||||
export const Context = createContext<ContextValue>(defaultContextValue);
|
||||
|
||||
const getFilteredSet =
|
||||
(
|
||||
setContext: Dispatch<SetStateAction<ContextValueOnly>>,
|
||||
contextKey: keyof ContextValueOnly,
|
||||
) =>
|
||||
(e: SetStateAction<any>) =>
|
||||
setContext(state => ({
|
||||
...state,
|
||||
[contextKey]: typeof e === 'function' ? e(state[contextKey]) : e,
|
||||
}));
|
||||
|
||||
export const ContextProvider = ({ children }: { children: JSX.Element }) => {
|
||||
const [context, setContext] = React.useState<ContextValueOnly>(
|
||||
defaultContextValueOnly,
|
||||
);
|
||||
const {
|
||||
setCollapsed,
|
||||
setNumVisitsOpen,
|
||||
setNumVisitsTotal,
|
||||
setVisits,
|
||||
setLoading,
|
||||
setKind,
|
||||
} = useMemo(
|
||||
() => ({
|
||||
setCollapsed: getFilteredSet(setContext, 'collapsed'),
|
||||
setNumVisitsOpen: getFilteredSet(setContext, 'numVisitsOpen'),
|
||||
setNumVisitsTotal: getFilteredSet(setContext, 'numVisitsTotal'),
|
||||
setVisits: getFilteredSet(setContext, 'visits'),
|
||||
setLoading: getFilteredSet(setContext, 'loading'),
|
||||
setKind: getFilteredSet(setContext, 'kind'),
|
||||
}),
|
||||
[setContext],
|
||||
);
|
||||
|
||||
const value: ContextValue = {
|
||||
...context,
|
||||
setContext,
|
||||
setCollapsed,
|
||||
setNumVisitsOpen,
|
||||
setNumVisitsTotal,
|
||||
setVisits,
|
||||
setLoading,
|
||||
setKind,
|
||||
};
|
||||
|
||||
return <Context.Provider value={value}>{children}</Context.Provider>;
|
||||
};
|
||||
|
||||
export const useContext = () => {
|
||||
const value = React.useContext(Context);
|
||||
|
||||
if (value === undefined)
|
||||
throw new Error(
|
||||
'VisitedByType useContext found undefined ContextValue, <ContextProvider/> could be missing',
|
||||
);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export default Context;
|
||||
+74
-20
@@ -18,10 +18,17 @@ import React from 'react';
|
||||
import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { ComponentType, PropsWithChildren } from 'react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import { HomePageRecentlyVisited } from '../../plugin';
|
||||
import { HomePageVisitedByType } from '../../plugin';
|
||||
import { Visit, visitsApiRef } from '../../api/VisitsApi';
|
||||
|
||||
const visits: Array<Visit> = [
|
||||
{
|
||||
id: 'tech-radar',
|
||||
name: 'Tech Radar',
|
||||
pathname: '/tech-radar',
|
||||
hits: 40,
|
||||
timestamp: Date.now() - 360_000,
|
||||
},
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
@@ -73,17 +80,10 @@ const visits: Array<Visit> = [
|
||||
id: 'hello-world',
|
||||
name: 'Hello World gRPC',
|
||||
pathname: '/catalog/default/api/hello-world',
|
||||
hits: 5,
|
||||
hits: 1,
|
||||
timestamp: Date.now() - 86400_000 * 7,
|
||||
entityRef: 'API:default/hello-world',
|
||||
},
|
||||
{
|
||||
id: 'tech-radar',
|
||||
name: 'Tech Radar',
|
||||
pathname: '/tech-radar',
|
||||
hits: 1,
|
||||
timestamp: Date.now() - 360_000,
|
||||
},
|
||||
];
|
||||
|
||||
const mockVisitsApi = {
|
||||
@@ -92,7 +92,7 @@ const mockVisitsApi = {
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Home/Components/RecentlyVisited',
|
||||
title: 'Plugins/Home/Components/VisitedByType',
|
||||
decorators: [
|
||||
(Story: ComponentType<PropsWithChildren<{}>>) =>
|
||||
wrapInTestApp(
|
||||
@@ -103,42 +103,96 @@ export default {
|
||||
],
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
export const RecentlyDefault = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageRecentlyVisited />
|
||||
<HomePageVisitedByType kind="recent" />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const Empty = () => {
|
||||
export const RecentlyEmpty = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageRecentlyVisited visits={[]} />
|
||||
<HomePageVisitedByType kind="recent" visits={[]} />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const FewItems = () => {
|
||||
export const RecentlyFewItems = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageRecentlyVisited visits={visits.slice(0, 1)} />
|
||||
<HomePageVisitedByType kind="recent" visits={visits.slice(0, 1)} />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const MoreItems = () => {
|
||||
export const RecentlyMoreItems = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageRecentlyVisited numVisitsOpen={5} numVisitsTotal={6} />
|
||||
<HomePageVisitedByType
|
||||
kind="recent"
|
||||
numVisitsOpen={5}
|
||||
numVisitsTotal={6}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const Loading = () => {
|
||||
export const RecentlyLoading = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageRecentlyVisited numVisitsOpen={5} numVisitsTotal={6} loading />
|
||||
<HomePageVisitedByType
|
||||
kind="recent"
|
||||
numVisitsOpen={5}
|
||||
numVisitsTotal={6}
|
||||
loading
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const TopDefault = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageVisitedByType kind="top" />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const TopEmpty = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageVisitedByType kind="top" visits={[]} />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const TopFewItems = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageVisitedByType kind="top" visits={visits.slice(0, 1)} />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const TopMoreItems = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageVisitedByType kind="top" numVisitsOpen={5} numVisitsTotal={6} />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const TopLoading = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageVisitedByType
|
||||
kind="top"
|
||||
numVisitsOpen={5}
|
||||
numVisitsTotal={6}
|
||||
loading
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { VisitedByType } from './VisitedByType';
|
||||
import { Context, defaultContextValue } from './Context';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
||||
describe('<VisitedByType/> kind="top"', () => {
|
||||
it('should render', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Context.Provider value={{ ...defaultContextValue, kind: 'top' }}>
|
||||
<VisitedByType />
|
||||
</Context.Provider>,
|
||||
);
|
||||
expect(getByText('Top Visited')).toBeInTheDocument();
|
||||
});
|
||||
it('should display hits', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Context.Provider
|
||||
value={{
|
||||
...defaultContextValue,
|
||||
kind: 'top',
|
||||
loading: false,
|
||||
visits: [
|
||||
{
|
||||
id: 'tech-radar',
|
||||
name: 'Tech Radar',
|
||||
pathname: '/tech-radar',
|
||||
hits: 40,
|
||||
timestamp: Date.now() - 360_000,
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<VisitedByType />
|
||||
</Context.Provider>,
|
||||
);
|
||||
await waitFor(() => expect(getByText('40 times')).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
|
||||
describe('<VisitedByType/> kind="recent"', () => {
|
||||
it('should render', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Context.Provider value={{ ...defaultContextValue, kind: 'recent' }}>
|
||||
<VisitedByType />
|
||||
</Context.Provider>,
|
||||
);
|
||||
expect(getByText('Recently Visited')).toBeInTheDocument();
|
||||
});
|
||||
it('should display how long ago a visit happened', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Context.Provider
|
||||
value={{
|
||||
...defaultContextValue,
|
||||
kind: 'recent',
|
||||
loading: false,
|
||||
visits: [
|
||||
{
|
||||
id: 'tech-radar',
|
||||
name: 'Tech Radar',
|
||||
pathname: '/tech-radar',
|
||||
hits: 40,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<VisitedByType />
|
||||
</Context.Provider>,
|
||||
);
|
||||
await waitFor(() => expect(getByText('1 day ago')).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
+5
-8
@@ -17,19 +17,16 @@
|
||||
import React from 'react';
|
||||
import { VisitList } from '../../components/VisitList';
|
||||
import { useContext } from './Context';
|
||||
/**
|
||||
* Display recently visited pages for the homepage
|
||||
* @public
|
||||
*/
|
||||
export const RecentlyVisited = () => {
|
||||
const { collapsed, numVisitsOpen, numVisitsTotal, visits, loading } =
|
||||
|
||||
export const VisitedByType = () => {
|
||||
const { collapsed, numVisitsOpen, numVisitsTotal, visits, loading, kind } =
|
||||
useContext();
|
||||
|
||||
return (
|
||||
<VisitList
|
||||
visits={visits}
|
||||
title="Recently visited"
|
||||
detailType="time-ago"
|
||||
title={kind === 'top' ? 'Top Visited' : 'Recently Visited'}
|
||||
detailType={kind === 'top' ? 'hits' : 'time-ago'}
|
||||
collapsed={collapsed}
|
||||
numVisitsOpen={numVisitsOpen}
|
||||
numVisitsTotal={numVisitsTotal}
|
||||
+1
-1
@@ -17,4 +17,4 @@
|
||||
export { Content } from './Content';
|
||||
export { Actions } from './Actions';
|
||||
export { ContextProvider } from './Context';
|
||||
export type { RecentlyVisitedProps } from './Content';
|
||||
export type { VisitedByTypeProps, VisitedByTypeKind } from './Content';
|
||||
@@ -17,4 +17,4 @@
|
||||
export type { ToolkitContentProps, Tool } from './Toolkit';
|
||||
export type { ClockConfig } from './HeaderWorldClock';
|
||||
export type { WelcomeTitleLanguageProps } from './WelcomeTitle';
|
||||
export type { RecentlyVisitedProps } from './RecentlyVisited';
|
||||
export type { VisitedByTypeProps, VisitedByTypeKind } from './VisitedByType';
|
||||
|
||||
@@ -32,6 +32,7 @@ export {
|
||||
ComponentTab,
|
||||
WelcomeTitle,
|
||||
HeaderWorldClock,
|
||||
HomePageVisitedByType,
|
||||
} from './plugin';
|
||||
export * from './components';
|
||||
export * from './assets';
|
||||
|
||||
@@ -20,10 +20,7 @@ import {
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { createCardExtension } from '@backstage/plugin-home-react';
|
||||
import {
|
||||
ToolkitContentProps,
|
||||
RecentlyVisitedProps,
|
||||
} from './homePageComponents';
|
||||
import { ToolkitContentProps, VisitedByTypeProps } from './homePageComponents';
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
/** @public */
|
||||
@@ -177,12 +174,12 @@ export const HeaderWorldClock = homePlugin.provide(
|
||||
);
|
||||
|
||||
/**
|
||||
* Display recently visited pages for the homepage
|
||||
* Display recently/top visited pages for the homepage
|
||||
* @public
|
||||
*/
|
||||
export const HomePageRecentlyVisited = homePlugin.provide(
|
||||
createCardExtension<RecentlyVisitedProps>({
|
||||
name: 'HomePageRecentlyVisited',
|
||||
components: () => import('./homePageComponents/RecentlyVisited'),
|
||||
export const HomePageVisitedByType = homePlugin.provide(
|
||||
createCardExtension<VisitedByTypeProps>({
|
||||
name: 'HomePageVisitedByType',
|
||||
components: () => import('./homePageComponents/VisitedByType'),
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user