feat: add optional enrichVisit function onto VisitListener to add fields to customize chips
Signed-off-by: Stephanie Swaney <stephanie.swaney.ddk6@statefarm.com> feat: add VisitInput which has visit before auto saved fields, used to enrich in consuming app Signed-off-by: Stephanie Swaney <stephanie.swaney.ddk6@statefarm.com> chore: Rename ItemCategoryContext to VisitDisplayContext fix: rename the remainder to VisitDisplayContext from ItemCategoryContext Signed-off-by: Stephanie Swaney <stephanie.swaney.ddk6@statefarm.com> test: Add tests for VisitListener component
This commit is contained in:
@@ -105,7 +105,6 @@ export default _default;
|
||||
export const homeTranslationRef: TranslationRef<
|
||||
'home',
|
||||
{
|
||||
readonly 'starredEntities.noStarredEntitiesMessage': 'Click the star beside an entity name to add it to this list!';
|
||||
readonly 'addWidgetDialog.title': 'Add new widget to dashboard';
|
||||
readonly 'customHomepageButtons.clearAll': 'Clear all';
|
||||
readonly 'customHomepageButtons.edit': 'Edit';
|
||||
@@ -124,6 +123,7 @@ export const homeTranslationRef: TranslationRef<
|
||||
readonly 'quickStart.title': 'Onboarding';
|
||||
readonly 'quickStart.description': 'Get started with Backstage';
|
||||
readonly 'quickStart.learnMoreLinkTitle': 'Learn more';
|
||||
readonly 'starredEntities.noStarredEntitiesMessage': 'Click the star beside an entity name to add it to this list!';
|
||||
readonly 'visitedByType.action.viewMore': 'View more';
|
||||
readonly 'visitedByType.action.viewLess': 'View less';
|
||||
readonly 'featuredDocsCard.empty.title': 'No documents to show';
|
||||
|
||||
@@ -117,6 +117,12 @@ export type FeaturedDocsCardProps = {
|
||||
subLinkText?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type GetChipColorFunction = (visit: Visit) => string;
|
||||
|
||||
// @public
|
||||
export type GetLabelFunction = (visit: Visit) => string;
|
||||
|
||||
// @public
|
||||
export const HeaderWorldClock: (props: {
|
||||
clockConfigs: ClockConfig[];
|
||||
@@ -245,6 +251,9 @@ export type ToolkitContentProps = {
|
||||
tools: Tool[];
|
||||
};
|
||||
|
||||
// @public
|
||||
export const useVisitDisplay: () => VisitDisplayContextValue;
|
||||
|
||||
// @public
|
||||
export type Visit = {
|
||||
id: string;
|
||||
@@ -255,6 +264,31 @@ export type Visit = {
|
||||
entityRef?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface VisitDisplayContextValue {
|
||||
// (undocumented)
|
||||
getChipColor: GetChipColorFunction;
|
||||
// (undocumented)
|
||||
getLabel: GetLabelFunction;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const VisitDisplayProvider: ({
|
||||
children,
|
||||
getChipColor,
|
||||
getLabel,
|
||||
}: VisitDisplayProviderProps) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export interface VisitDisplayProviderProps {
|
||||
// (undocumented)
|
||||
children: ReactNode;
|
||||
// (undocumented)
|
||||
getChipColor?: GetChipColorFunction;
|
||||
// (undocumented)
|
||||
getLabel?: GetLabelFunction;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type VisitedByTypeKind = 'recent' | 'top';
|
||||
|
||||
@@ -267,15 +301,29 @@ export type VisitedByTypeProps = {
|
||||
kind: VisitedByTypeKind;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type VisitEnrichmentFunction = (
|
||||
visit: VisitInput,
|
||||
) => Record<string, any> | Promise<Record<string, any>>;
|
||||
|
||||
// @public
|
||||
export type VisitInput = {
|
||||
name: string;
|
||||
pathname: string;
|
||||
entityRef?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const VisitListener: ({
|
||||
children,
|
||||
toEntityRef,
|
||||
visitName,
|
||||
enrichVisit,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
toEntityRef?: ({ pathname }: { pathname: string }) => string | undefined;
|
||||
visitName?: ({ pathname }: { pathname: string }) => string;
|
||||
enrichVisit?: VisitEnrichmentFunction;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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 { createContext, useContext, ReactNode } from 'react';
|
||||
import { CompoundEntityRef, parseEntityRef } from '@backstage/catalog-model';
|
||||
import { colorVariants } from '@backstage/theme';
|
||||
import { Visit } from '../../api/VisitsApi';
|
||||
|
||||
/**
|
||||
* Type definition for the chip color function
|
||||
* @public
|
||||
*/
|
||||
export type GetChipColorFunction = (visit: Visit) => string;
|
||||
|
||||
/**
|
||||
* Type definition for the label function
|
||||
* @public
|
||||
*/
|
||||
export type GetLabelFunction = (visit: Visit) => string;
|
||||
|
||||
/**
|
||||
* Context value interface
|
||||
* @public
|
||||
*/
|
||||
export interface VisitDisplayContextValue {
|
||||
getChipColor: GetChipColorFunction;
|
||||
getLabel: GetLabelFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the VisitDisplayProvider
|
||||
* @public
|
||||
*/
|
||||
export interface VisitDisplayProviderProps {
|
||||
children: ReactNode;
|
||||
getChipColor?: GetChipColorFunction;
|
||||
getLabel?: GetLabelFunction;
|
||||
}
|
||||
|
||||
// Default implementations
|
||||
const getColorByIndex = (index: number) => {
|
||||
const variants = Object.keys(colorVariants);
|
||||
const variantIndex = index % variants.length;
|
||||
return colorVariants[variants[variantIndex]][0];
|
||||
};
|
||||
|
||||
const maybeEntity = (visit: Visit): CompoundEntityRef | undefined => {
|
||||
try {
|
||||
return parseEntityRef(visit?.entityRef ?? '');
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const defaultGetChipColor: GetChipColorFunction = (visit: Visit): string => {
|
||||
const defaultColor = getColorByIndex(0);
|
||||
const entity = maybeEntity(visit);
|
||||
if (!entity) return defaultColor;
|
||||
|
||||
// IDEA: Use or replicate useAllKinds hook thus supporting all software catalog
|
||||
// registered kinds. See:
|
||||
// plugins/catalog-react/src/components/EntityKindPicker/kindFilterUtils.ts
|
||||
// Provide extension point to register your own color code.
|
||||
const entityKinds = [
|
||||
'component',
|
||||
'template',
|
||||
'api',
|
||||
'group',
|
||||
'user',
|
||||
'resource',
|
||||
'system',
|
||||
'domain',
|
||||
'location',
|
||||
];
|
||||
const foundIndex = entityKinds.indexOf(
|
||||
entity.kind.toLocaleLowerCase('en-US'),
|
||||
);
|
||||
return foundIndex === -1 ? defaultColor : getColorByIndex(foundIndex + 1);
|
||||
};
|
||||
|
||||
const defaultGetLabel: GetLabelFunction = (visit: Visit): string => {
|
||||
const entity = maybeEntity(visit);
|
||||
return (entity?.kind ?? 'Other').toLocaleLowerCase('en-US');
|
||||
};
|
||||
|
||||
// Create the context
|
||||
const VisitDisplayContext = createContext<VisitDisplayContextValue>({
|
||||
getChipColor: defaultGetChipColor,
|
||||
getLabel: defaultGetLabel,
|
||||
});
|
||||
|
||||
/**
|
||||
* Provider component for VisitDisplay customization
|
||||
* @public
|
||||
*/
|
||||
export const VisitDisplayProvider = ({
|
||||
children,
|
||||
getChipColor = defaultGetChipColor,
|
||||
getLabel = defaultGetLabel,
|
||||
}: VisitDisplayProviderProps) => {
|
||||
const value: VisitDisplayContextValue = {
|
||||
getChipColor,
|
||||
getLabel,
|
||||
};
|
||||
|
||||
return (
|
||||
<VisitDisplayContext.Provider value={value}>
|
||||
{children}
|
||||
</VisitDisplayContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to use the VisitDisplay context
|
||||
* @public
|
||||
*/
|
||||
export const useVisitDisplay = (): VisitDisplayContextValue => {
|
||||
const context = useContext(VisitDisplayContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useVisitDisplay must be used within a VisitDisplayProvider',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { colorVariants } from '@backstage/theme';
|
||||
import { Visit } from '../../api/VisitsApi';
|
||||
import { CompoundEntityRef, parseEntityRef } from '@backstage/catalog-model';
|
||||
import { useVisitDisplay } from './Context';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
chip: {
|
||||
@@ -27,53 +26,17 @@ const useStyles = makeStyles(theme => ({
|
||||
margin: 0,
|
||||
},
|
||||
}));
|
||||
const maybeEntity = (visit: Visit): CompoundEntityRef | undefined => {
|
||||
try {
|
||||
return parseEntityRef(visit?.entityRef ?? '');
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const getColorByIndex = (index: number) => {
|
||||
const variants = Object.keys(colorVariants);
|
||||
const variantIndex = index % variants.length;
|
||||
return colorVariants[variants[variantIndex]][0];
|
||||
};
|
||||
const getChipColor = (entity: CompoundEntityRef | undefined): string => {
|
||||
const defaultColor = getColorByIndex(0);
|
||||
if (!entity) return defaultColor;
|
||||
|
||||
// IDEA: Use or replicate useAllKinds hook thus supporting all software catalog
|
||||
// registered kinds. See:
|
||||
// plugins/catalog-react/src/components/EntityKindPicker/kindFilterUtils.ts
|
||||
// Provide extension point to register your own color code.
|
||||
const entityKinds = [
|
||||
'component',
|
||||
'template',
|
||||
'api',
|
||||
'group',
|
||||
'user',
|
||||
'resource',
|
||||
'system',
|
||||
'domain',
|
||||
'location',
|
||||
];
|
||||
const foundIndex = entityKinds.indexOf(
|
||||
entity.kind.toLocaleLowerCase('en-US'),
|
||||
);
|
||||
return foundIndex === -1 ? defaultColor : getColorByIndex(foundIndex + 1);
|
||||
};
|
||||
|
||||
export const ItemCategory = ({ visit }: { visit: Visit }) => {
|
||||
const classes = useStyles();
|
||||
const entity = maybeEntity(visit);
|
||||
const { getChipColor, getLabel } = useVisitDisplay();
|
||||
|
||||
return (
|
||||
<Chip
|
||||
size="small"
|
||||
className={classes.chip}
|
||||
label={(entity?.kind ?? 'Other').toLocaleLowerCase('en-US')}
|
||||
style={{ background: getChipColor(entity) }}
|
||||
label={getLabel(visit)}
|
||||
style={{ background: getChipColor(visit) }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -39,6 +39,9 @@ const ItemDetailTimeAgo = ({ visit }: { visit: Visit }) => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type ItemDetailType = 'time-ago' | 'hits';
|
||||
|
||||
export const ItemDetail = ({
|
||||
|
||||
@@ -24,6 +24,9 @@ import { VisitListEmpty } from './VisitListEmpty';
|
||||
import { VisitListFew } from './VisitListFew';
|
||||
import { VisitListSkeleton } from './VisitListSkeleton';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const VisitList = ({
|
||||
detailType,
|
||||
visits = [],
|
||||
|
||||
@@ -14,4 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { VisitList } from './VisitList';
|
||||
// Public API exports
|
||||
export { VisitDisplayProvider, useVisitDisplay } from './Context';
|
||||
export type {
|
||||
GetChipColorFunction,
|
||||
GetLabelFunction,
|
||||
VisitDisplayContextValue,
|
||||
VisitDisplayProviderProps,
|
||||
} from './Context';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
|
||||
import { Visit, visitsApiRef } from '../api';
|
||||
import { VisitListener } from './VisitListener';
|
||||
import { VisitListener, VisitEnrichmentFunction } from './VisitListener';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
||||
const visits: Array<Visit> = [
|
||||
@@ -130,4 +130,145 @@ describe('<VisitListener/>', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe('requestId tests', () => {
|
||||
beforeEach(() => {
|
||||
// Mock requestAnimationFrame to execute immediately
|
||||
global.requestAnimationFrame = jest.fn(callback => {
|
||||
callback(0);
|
||||
return 1;
|
||||
});
|
||||
global.cancelAnimationFrame = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('uses requestAnimationFrame to defer visit saving', async () => {
|
||||
const pathname = '/catalog/default/component/test-component';
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<VisitListener />
|
||||
</TestApiProvider>,
|
||||
{ routeEntries: [pathname] },
|
||||
);
|
||||
|
||||
expect(global.requestAnimationFrame).toHaveBeenCalledTimes(1);
|
||||
await waitFor(() => expect(mockVisitsApi.save).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
it('saves base visit when no enrichment function is provided', async () => {
|
||||
const pathname = '/catalog/default/component/test-component';
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<VisitListener />
|
||||
</TestApiProvider>,
|
||||
{ routeEntries: [pathname] },
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockVisitsApi.save).toHaveBeenCalledWith({
|
||||
visit: {
|
||||
pathname,
|
||||
entityRef: 'component:default/test-component',
|
||||
name: 'test-component',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('enriches visit with additional data when enrichVisit function is provided', async () => {
|
||||
const pathname = '/catalog/default/component/test-component';
|
||||
const enrichVisit: VisitEnrichmentFunction = jest.fn(async _visit => ({
|
||||
customProperty: 'custom-value',
|
||||
category: 'test-category',
|
||||
priority: 1,
|
||||
}));
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<VisitListener enrichVisit={enrichVisit} />
|
||||
</TestApiProvider>,
|
||||
{ routeEntries: [pathname] },
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(enrichVisit).toHaveBeenCalledWith({
|
||||
pathname,
|
||||
entityRef: 'component:default/test-component',
|
||||
name: 'test-component',
|
||||
});
|
||||
expect(mockVisitsApi.save).toHaveBeenCalledWith({
|
||||
visit: {
|
||||
pathname,
|
||||
entityRef: 'component:default/test-component',
|
||||
name: 'test-component',
|
||||
customProperty: 'custom-value',
|
||||
category: 'test-category',
|
||||
priority: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles synchronous enrichment function', async () => {
|
||||
const pathname = '/catalog/default/component/test-component';
|
||||
const enrichVisit: VisitEnrichmentFunction = jest.fn(_visit => ({
|
||||
syncProperty: 'sync-value',
|
||||
}));
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<VisitListener enrichVisit={enrichVisit} />
|
||||
</TestApiProvider>,
|
||||
{ routeEntries: [pathname] },
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(enrichVisit).toHaveBeenCalledWith({
|
||||
pathname,
|
||||
entityRef: 'component:default/test-component',
|
||||
name: 'test-component',
|
||||
});
|
||||
expect(mockVisitsApi.save).toHaveBeenCalledWith({
|
||||
visit: {
|
||||
pathname,
|
||||
entityRef: 'component:default/test-component',
|
||||
name: 'test-component',
|
||||
syncProperty: 'sync-value',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('enrichment function can override base visit properties', async () => {
|
||||
const pathname = '/catalog/default/component/test-component';
|
||||
const enrichVisit: VisitEnrichmentFunction = jest.fn(async _visit => ({
|
||||
name: 'Overridden Name',
|
||||
entityRef: 'overridden:ref/value',
|
||||
customField: 'additional-data',
|
||||
}));
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[visitsApiRef, mockVisitsApi]]}>
|
||||
<VisitListener enrichVisit={enrichVisit} />
|
||||
</TestApiProvider>,
|
||||
{ routeEntries: [pathname] },
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockVisitsApi.save).toHaveBeenCalledWith({
|
||||
visit: {
|
||||
pathname,
|
||||
name: 'Overridden Name',
|
||||
entityRef: 'overridden:ref/value',
|
||||
customField: 'additional-data',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,6 +74,25 @@ const getVisitName =
|
||||
return document.title;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Type definition for visit data before it's saved (without auto-generated fields)
|
||||
*/
|
||||
export type VisitInput = {
|
||||
name: string;
|
||||
pathname: string;
|
||||
entityRef?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Type definition for the visit enrichment function
|
||||
* This allows adding custom properties to visits at save time
|
||||
*/
|
||||
export type VisitEnrichmentFunction = (
|
||||
visit: VisitInput,
|
||||
) => Record<string, any> | Promise<Record<string, any>>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Component responsible for listening to location changes and calling
|
||||
@@ -83,29 +102,46 @@ export const VisitListener = ({
|
||||
children,
|
||||
toEntityRef,
|
||||
visitName,
|
||||
enrichVisit,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
toEntityRef?: ({ pathname }: { pathname: string }) => string | undefined;
|
||||
visitName?: ({ pathname }: { pathname: string }) => string;
|
||||
enrichVisit?: VisitEnrichmentFunction;
|
||||
}): JSX.Element => {
|
||||
const visitsApi = useApi(visitsApiRef);
|
||||
const { pathname } = useLocation();
|
||||
const toEntityRefImpl = toEntityRef ?? getToEntityRef();
|
||||
const visitNameImpl = visitName ?? getVisitName();
|
||||
|
||||
useEffect(() => {
|
||||
// Wait for the browser to finish with paint with the assumption react
|
||||
// has finished with dom reconciliation.
|
||||
const requestId = requestAnimationFrame(() => {
|
||||
const requestId = requestAnimationFrame(async () => {
|
||||
const baseVisit = {
|
||||
name: visitNameImpl({ pathname }),
|
||||
pathname,
|
||||
entityRef: toEntityRefImpl({ pathname }),
|
||||
};
|
||||
|
||||
let visitToSave = baseVisit;
|
||||
|
||||
if (enrichVisit) {
|
||||
try {
|
||||
const enrichedData = await enrichVisit(baseVisit);
|
||||
visitToSave = { ...baseVisit, ...enrichedData };
|
||||
} catch (error) {
|
||||
// If enrichment fails, save the base visit without enrichment
|
||||
visitToSave = baseVisit;
|
||||
}
|
||||
}
|
||||
|
||||
visitsApi.save({
|
||||
visit: {
|
||||
name: visitNameImpl({ pathname }),
|
||||
pathname,
|
||||
entityRef: toEntityRefImpl({ pathname }),
|
||||
},
|
||||
visit: visitToSave,
|
||||
});
|
||||
});
|
||||
return () => cancelAnimationFrame(requestId);
|
||||
}, [visitsApi, pathname, toEntityRefImpl, visitNameImpl]);
|
||||
}, [visitsApi, pathname, toEntityRefImpl, visitNameImpl, enrichVisit]);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
@@ -17,3 +17,4 @@
|
||||
export { HomepageCompositionRoot } from './HomepageCompositionRoot';
|
||||
export * from './CustomHomepage';
|
||||
export * from './VisitListener';
|
||||
export * from './VisitList';
|
||||
|
||||
@@ -25,23 +25,23 @@ import {
|
||||
import { Visit } from '../../api/VisitsApi';
|
||||
import { VisitedByTypeKind } from './Content';
|
||||
|
||||
export type ContextValueOnly = {
|
||||
export type ContextValueOnly<T = Visit> = {
|
||||
collapsed: boolean;
|
||||
numVisitsOpen: number;
|
||||
numVisitsTotal: number;
|
||||
visits: Array<Visit>;
|
||||
visits: Array<T>;
|
||||
loading: boolean;
|
||||
kind: VisitedByTypeKind;
|
||||
};
|
||||
|
||||
export type ContextValue = ContextValueOnly & {
|
||||
export type ContextValue<T = Visit> = ContextValueOnly<T> & {
|
||||
setCollapsed: Dispatch<SetStateAction<boolean>>;
|
||||
setNumVisitsOpen: Dispatch<SetStateAction<number>>;
|
||||
setNumVisitsTotal: Dispatch<SetStateAction<number>>;
|
||||
setVisits: Dispatch<SetStateAction<Array<Visit>>>;
|
||||
setVisits: Dispatch<SetStateAction<Array<T>>>;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
setKind: Dispatch<SetStateAction<VisitedByTypeKind>>;
|
||||
setContext: Dispatch<SetStateAction<ContextValueOnly>>;
|
||||
setContext: Dispatch<SetStateAction<ContextValueOnly<T>>>;
|
||||
};
|
||||
|
||||
const defaultContextValueOnly: ContextValueOnly = {
|
||||
@@ -79,9 +79,9 @@ const getFilteredSet =
|
||||
}));
|
||||
|
||||
export const ContextProvider = ({ children }: { children: JSX.Element }) => {
|
||||
const [context, setContext] = useState<ContextValueOnly>(
|
||||
defaultContextValueOnly,
|
||||
);
|
||||
const [context, setContext] = useState<ContextValueOnly>({
|
||||
...defaultContextValueOnly,
|
||||
});
|
||||
const {
|
||||
setCollapsed,
|
||||
setNumVisitsOpen,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { VisitList } from '../../components/VisitList';
|
||||
import { VisitList } from '../../components/VisitList/VisitList';
|
||||
import { useContext } from './Context';
|
||||
|
||||
export const VisitedByType = () => {
|
||||
|
||||
Reference in New Issue
Block a user