From 2739de3fdc308e153043bbc1c656c3e537038c5a Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Tue, 24 Nov 2020 18:16:13 -0500 Subject: [PATCH] stack rank product panels --- plugins/cost-insights/src/client.ts | 6 +- .../ProductInsights/ProductInsights.tsx | 8 +- .../ProductInsightsCard.test.tsx | 43 ++-- .../ProductInsightsCard.tsx | 156 +++++++-------- .../ProductInsightsCardList.test.tsx | 156 +++++++++++++++ .../ProductInsightsCardList.tsx | 188 ++++++++++++++++++ .../ProductInsightsErrorCard.tsx | 50 +++++ .../components/ProductInsightsCard/index.ts | 2 +- .../cost-insights/src/hooks/useLoading.tsx | 9 +- plugins/cost-insights/src/utils/loading.ts | 10 +- plugins/cost-insights/src/utils/mockData.ts | 4 +- 11 files changed, 499 insertions(+), 133 deletions(-) create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/client.ts index 2180385424..db999584a0 100644 --- a/plugins/cost-insights/src/client.ts +++ b/plugins/cost-insights/src/client.ts @@ -332,14 +332,14 @@ export class ExampleCostInsightsClient implements CostInsightsApi { project: 'example-project', periodStart: '2020-Q2', periodEnd: '2020-Q3', - aggregation: [60_000, 120_000], + aggregation: [100, 120_000], change: { - ratio: 1, + ratio: 10.123123, amount: 60000, }, products: [ { id: 'Compute Engine', aggregation: [58_000, 118_000] }, - { id: 'Cloud Dataflow', aggregation: [1200, 1500] }, + { id: 'Cloud Dataflow', aggregation: [0, 15_000] }, { id: 'Cloud Storage', aggregation: [800, 500] }, ], }; diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx index c5d39a5139..affef5d182 100644 --- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx +++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { Box, Typography, Grid } from '@material-ui/core'; -import { ProductInsightsCard } from '../ProductInsightsCard'; +import { ProductInsightsCardList } from '../ProductInsightsCard'; import { useConfig } from '../../hooks'; export const ProductInsights = ({}) => { @@ -30,11 +30,7 @@ export const ProductInsights = ({}) => { - {config.products.map(product => ( - - - - ))} + ); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx index cdbbb79806..385ff82f9b 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx @@ -22,19 +22,19 @@ import { createMockEntity, mockDefaultLoadingState, MockComputeEngine, - MockProductFilters, } from '../../utils/mockData'; import { - MockCostInsightsApiProvider, - MockBillingDateProvider, MockConfigProvider, + MockCostInsightsApiProvider, MockCurrencyProvider, - MockFilterProvider, - MockGroupsProvider, + MockBillingDateProvider, MockScrollProvider, MockLoadingProvider, } from '../../utils/tests'; -import { Duration, Entity, Product, ProductPeriod } from '../../types'; +import { Duration, Entity, Product } from '../../types'; + +// suppress recharts componentDidUpdate warnings +jest.spyOn(console, 'warn').mockImplementation(() => {}); const costInsightsApi = (entity: Entity): Partial => ({ getProductInsights: () => Promise.resolve(entity), @@ -53,29 +53,25 @@ const mockProductCost = createMockEntity(() => ({ const renderProductInsightsCardInTestApp = async ( entity: Entity, product: Product, - duration: Duration, + duration = Duration.P30D, + onSelectAsync = jest.fn(() => Promise.resolve(mockProductCost)), ) => await renderInTestApp( - - + + - ({ - ...p, - duration: duration, - }))} - > - - - - - - + + + - - + + , ); @@ -100,7 +96,6 @@ describe('', () => { const rendered = await renderProductInsightsCardInTestApp( entity, MockComputeEngine, - Duration.P1M, ); const subheader = 'entities, sorted by cost'; const subheaderRgx = new RegExp(`${entity.entities.length} ${subheader}`); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx index 2a1f962000..6d6d8d5cd9 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx @@ -14,46 +14,84 @@ * limitations under the License. */ -import React, { useCallback, useEffect, useState } from 'react'; -import { InfoCard, useApi } from '@backstage/core'; -import Alert from '@material-ui/lab/Alert'; +import React, { + PropsWithChildren, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; +import { InfoCard } from '@backstage/core'; import { Typography } from '@material-ui/core'; -import { costInsightsApiRef } from '../../api'; -import { ProductInsightsChart } from './ProductInsightsChart'; import { PeriodSelect } from '../PeriodSelect'; -import { - useFilters, - useLastCompleteBillingDate, - useLoading, - useScroll, -} from '../../hooks'; +import { ProductInsightsChart } from './ProductInsightsChart'; +import { ProductInsightsErrorCard } from './ProductInsightsErrorCard'; import { useProductInsightsCardStyles as useStyles } from '../../utils/styles'; -import { mapFiltersToProps, mapLoadingToProps } from './selector'; -import { Duration, Maybe, Product, Entity } from '../../types'; +import { DefaultLoadingAction } from '../../utils/loading'; +import { Duration, Entity, Maybe, Product } from '../../types'; +import { + useLastCompleteBillingDate, + useScroll, + useLoading, + MapLoadingToProps, +} from '../../hooks'; import { pluralOf } from '../../utils/grammar'; -type ProductInsightsCardProps = { +type LoadingProps = (isLoading: boolean) => void; + +export type ProductInsightsCardProps = { product: Product; + initialState: { + entity: Maybe; + duration: Duration; + }; + onSelectAsync: (product: Product, duration: Duration) => Promise; }; -export const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => { - const client = useApi(costInsightsApiRef); +const mapLoadingToProps: MapLoadingToProps = ({ dispatch }) => ( + isLoading: boolean, +) => dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading }); + +export const ProductInsightsCard = ({ + initialState, + product, + onSelectAsync, +}: PropsWithChildren) => { const classes = useStyles(); + const mountedRef = useRef(false); const { ScrollAnchor } = useScroll(product.kind); - const lastCompleteBillingDate = useLastCompleteBillingDate(); - const [entity, setEntity] = useState>(null); const [error, setError] = useState>(null); + const dispatchLoading = useLoading(mapLoadingToProps); + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const [entity, setEntity] = useState>(initialState.entity); + const [duration, setDuration] = useState(initialState.duration); - const { group, product: productFilter, setProduct, project } = useFilters( - mapFiltersToProps(product.kind), - ); - const { loadingProduct, dispatchLoading } = useLoading( - mapLoadingToProps(product.kind), - ); + /* eslint-disable react-hooks/exhaustive-deps */ + const dispatchLoadingProduct = useCallback(dispatchLoading, []); + /* eslint-enable react-hooks/exhaustive-deps */ - // @see CostInsightsPage - // eslint-disable-next-line react-hooks/exhaustive-deps - const dispatchLoadingProduct = useCallback(dispatchLoading, [product.kind]); + useEffect(() => { + async function handleOnSelectAsync() { + dispatchLoadingProduct(true); + try { + const e = await onSelectAsync(product, duration); + setEntity(e); + } catch (e) { + setEntity(null); + setError(e); + } finally { + dispatchLoadingProduct(false); + } + } + + if (mountedRef.current) { + handleOnSelectAsync(); + } + }, [product, duration, onSelectAsync, dispatchLoadingProduct]); + + useEffect(function hasComponentMounted() { + mountedRef.current = true; + }, []); const amount = entity?.entities?.length || 0; const hasCostsWithinTimeframe = !!(entity?.change && amount); @@ -62,77 +100,31 @@ export const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => { ? `${amount} ${pluralOf(amount, 'entity', 'entities')}, sorted by cost` : null; - useEffect(() => { - async function load() { - if (loadingProduct) { - try { - const e: Entity = await client.getProductInsights({ - product: product.kind, - group: group!, - duration: productFilter!.duration, - lastCompleteBillingDate, - project, - }); - setEntity(e); - } catch (e) { - setError(e); - } finally { - dispatchLoadingProduct(false); - } - } - } - load(); - }, [ - client, - product, - setEntity, - loadingProduct, - dispatchLoadingProduct, - productFilter, - group, - product.kind, - project, - lastCompleteBillingDate, - ]); - - const onPeriodSelect = (duration: Duration) => { - dispatchLoadingProduct(true); - setProduct(duration); - }; - const infoCardProps = { headerProps: { classes: classes, - action: ( - - ), + action: , }, }; - if (error) { + if (error || !entity) { return ( - - - {`Error: Could not fetch product insights for ${product.name}`} - + ); } - if (!entity) { - return null; - } - return ( {hasCostsWithinTimeframe ? ( ) : ( diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx new file mode 100644 index 0000000000..256ee8ba96 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx @@ -0,0 +1,156 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { renderInTestApp } from '@backstage/test-utils'; +import { ProductInsightsCardList } from './ProductInsightsCardList'; +import { ProductInsightsOptions } from '../../api'; +import { mockDefaultLoadingState, MockProducts } from '../../utils/mockData'; +import { + MockConfigProvider, + MockCostInsightsApiProvider, + MockCurrencyProvider, + MockFilterProvider, + MockBillingDateProvider, + MockScrollProvider, + MockLoadingProvider, +} from '../../utils/tests'; +import { Entity } from '../../types'; + +// suppress recharts componentDidUpdate warnings +jest.spyOn(console, 'warn').mockImplementation(() => {}); + +const MockComputeEngineInsights: Entity = { + id: 'compute-engine', + entities: [], + aggregation: [0, 0], + change: { + ratio: 0, + amount: 0, + }, +}; + +const MockCloudDataflowInsights: Entity = { + id: 'cloud-dataflow', + entities: [], + aggregation: [1_000, 2_000], + change: { + ratio: 1, + amount: 1_000, + }, +}; + +const MockCloudStorageInsights: Entity = { + id: 'cloud-storage', + entities: [], + aggregation: [2_000, 4_000], + change: { + ratio: 1, + amount: 2_000, + }, +}; + +const MockBigQueryInsights: Entity = { + id: 'big-query', + entities: [], + aggregation: [8_000, 16_000], + change: { + ratio: 1, + amount: 8_000, + }, +}; + +const MockBigTableInsights: Entity = { + id: 'big-table', + entities: [], + aggregation: [16_000, 32_000], + change: { + ratio: 1, + amount: 16_000, + }, +}; + +const MockCloudPubSubInsights: Entity = { + id: 'cloud-pub-sub', + entities: [], + aggregation: [32_000, 64_000], + change: { + ratio: 1, + amount: 32_000, + }, +}; + +const ProductEntityMap = { + [MockBigQueryInsights.id!]: MockBigQueryInsights, + [MockBigTableInsights.id!]: MockBigTableInsights, + [MockCloudPubSubInsights.id!]: MockCloudPubSubInsights, + [MockCloudStorageInsights.id!]: MockCloudStorageInsights, + [MockCloudDataflowInsights.id!]: MockCloudDataflowInsights, + [MockComputeEngineInsights.id!]: MockComputeEngineInsights, +}; + +const costInsightsApi = { + getProductInsights: ({ product }: ProductInsightsOptions): Promise => + Promise.resolve(ProductEntityMap[product]), +}; + +function renderInContext(children: JSX.Element) { + return renderInTestApp( + + + + + + + {children} + + + + + + , + ); +} + +describe('', () => { + it('should render each product panel', async () => { + const noComputeEngineCostsRgx = /There are no Compute Engine costs within this timeframe for your team's projects./; + const { getByText } = await renderInContext( + , + ); + expect(getByText(noComputeEngineCostsRgx)).toBeInTheDocument(); + MockProducts.forEach(product => + expect(getByText(product.name)).toBeInTheDocument(), + ); + }); + + it('product panels should be sorted by total aggregated cost', async () => { + const { queryAllByTestId } = await renderInContext( + , + ); + const expectedOrder = MockProducts.map( + product => `product-list-item-${product.kind}`, + ).reverse(); + const productPanels = queryAllByTestId(/^product-list-item/); + + expect(productPanels.length).toBe(MockProducts.length); + Array.from(productPanels) + .map(el => el.getAttribute('data-testid')) + .forEach((id, i) => { + expect(id).toBe(expectedOrder[i]); + }); + }); +}); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx new file mode 100644 index 0000000000..5c096553e1 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx @@ -0,0 +1,188 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { useCallback, useEffect, useReducer, useRef } from 'react'; +import { Box, CircularProgress } from '@material-ui/core'; +import { useApi } from '@backstage/core'; +import { costInsightsApiRef } from '../../api'; +import { ProductInsightsCard } from './ProductInsightsCard'; +import { Duration, Entity, Maybe, Product } from '../../types'; +import { DefaultLoadingAction } from '../../utils/loading'; +import { + useFilters, + useLoading, + useLastCompleteBillingDate, + MapLoadingToProps, +} from '../../hooks'; + +type State = { + [kind: string]: ProductState; +}; + +type ProductState = { + product: Product; + entity: Maybe; + duration: Duration; +}; + +type LoadingProps = (isLoading: boolean) => void; + +type ProductInsightsCardListProps = { + products: Product[]; +}; + +const DEFAULT_DURATION = Duration.P30D; + +function totalAggregationSort(a: ProductState, b: ProductState): number { + const [prevA, currA] = a.entity?.aggregation ?? [0, 0]; + const [prevB, currB] = b.entity?.aggregation ?? [0, 0]; + return prevB + currB - (prevA + currA); +} + +const mapLoadingToProps: MapLoadingToProps = ({ dispatch }) => ( + isLoading: boolean, +) => dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading }); + +function reducer(prevState: State, action: State): State { + return { + ...prevState, + ...action, + }; +} + +export const ProductInsightsCardList = ({ + products, +}: ProductInsightsCardListProps) => { + const onceRef = useRef(false); + const client = useApi(costInsightsApiRef); + const filters = useFilters(p => p.pageFilters); + const [state, dispatch] = useReducer(reducer, {}); + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const dispatchLoading = useLoading(mapLoadingToProps); + + /* eslint-disable react-hooks/exhaustive-deps */ + // See @CostInsightsPage + const dispatchLoadingProducts = useCallback(dispatchLoading, []); + /* eslint-enable react-hooks/exhaustive-deps */ + + const initialProductStates = onceRef.current + ? Object.values(state).sort(totalAggregationSort) + : Object.values(state); + + const onSelectAsyncMemo = useCallback( + async function onSelectAsync( + product: Product, + duration: Duration, + ): Promise { + if (filters.group) { + return client.getProductInsights({ + group: filters.group, + project: filters.project, + product: product.kind, + duration: duration, + lastCompleteBillingDate: lastCompleteBillingDate, + }); + } + return Promise.reject( + new Error( + `Cannot fetch insights for ${product.name}: Expected to have a group but found none.`, + ), + ); + }, + [client, filters.group, filters.project, lastCompleteBillingDate], + ); + + useEffect(() => { + async function getAllProductInsights( + products: Product[], + group: string, + project: Maybe, + lastCompleteBillingDate: string, + ) { + const requests = products.map(product => + client.getProductInsights({ + group: group, + project: project, + product: product.kind, + duration: DEFAULT_DURATION, + lastCompleteBillingDate: lastCompleteBillingDate, + }), + ); + + try { + dispatchLoadingProducts(true); + const responses = await Promise.allSettled(requests); + const results = responses.map(response => + response.status === 'fulfilled' ? response.value : null, + ); + const initialState = results.reduce((acc, entity, index) => { + const product = products[index]; + return { + ...acc, + [product.kind]: { + product: product, + entity: entity, + duration: DEFAULT_DURATION, + }, + }; + }, {}); + dispatch(initialState); + } finally { + dispatchLoadingProducts(false); + } + } + + if (filters.group) { + onceRef.current = true; + getAllProductInsights( + products, + filters.group, + filters.project, + lastCompleteBillingDate, + ); + } + }, [ + client, + products, + filters.group, + filters.project, + lastCompleteBillingDate, + dispatchLoadingProducts, + ]); + + if (!initialProductStates.length) { + return ; + } + + return ( + <> + {initialProductStates.map(({ product, entity, duration }) => ( + + + + ))} + + ); +}; diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx new file mode 100644 index 0000000000..6f6c5a21c8 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { InfoCard } from '@backstage/core'; +import { default as Alert } from '@material-ui/lab/Alert'; +import { PeriodSelect } from '../PeriodSelect'; +import { useScroll } from '../../hooks'; +import { useProductInsightsCardStyles as useStyles } from '../../utils/styles'; +import { Duration, Product } from '../../types'; + +export type ProductInsightsErrorCardProps = { + product: Product; + duration: Duration; + onSelect: (duration: Duration) => void; +}; + +export const ProductInsightsErrorCard = ({ + product, + duration, + onSelect, +}: ProductInsightsErrorCardProps) => { + const classes = useStyles(); + const { ScrollAnchor } = useScroll(product.kind); + + const headerProps = { + classes: classes, + action: , + }; + + return ( + + + {`Error: Could not fetch product insights for ${product.name}`} + + ); +}; diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/index.ts b/plugins/cost-insights/src/components/ProductInsightsCard/index.ts index 33a402234c..0a67bf459e 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/index.ts +++ b/plugins/cost-insights/src/components/ProductInsightsCard/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { ProductInsightsCard } from './ProductInsightsCard'; +export { ProductInsightsCardList } from './ProductInsightsCardList'; export { ProductInsightsChart } from './ProductInsightsChart'; diff --git a/plugins/cost-insights/src/hooks/useLoading.tsx b/plugins/cost-insights/src/hooks/useLoading.tsx index 420ac7ed56..07f8a0f4e7 100644 --- a/plugins/cost-insights/src/hooks/useLoading.tsx +++ b/plugins/cost-insights/src/hooks/useLoading.tsx @@ -21,7 +21,6 @@ import React, { SetStateAction, useContext, useEffect, - useMemo, useReducer, useState, } from 'react'; @@ -30,10 +29,9 @@ import { Loading } from '../types'; import { DefaultLoadingAction, getDefaultState, - getLoadingActions, + INITIAL_LOADING_ACTIONS, } from '../utils/loading'; import { useBackdropStyles as useStyles } from '../utils/styles'; -import { useConfig } from './useConfig'; export type LoadingContextProps = { state: Loading; @@ -56,10 +54,7 @@ function reducer(prevState: Loading, action: Partial): Loading { export const LoadingProvider = ({ children }: PropsWithChildren<{}>) => { const classes = useStyles(); - const { products } = useConfig(); - const actions = useMemo(() => getLoadingActions(products.map(p => p.kind)), [ - products, - ]); + const actions = INITIAL_LOADING_ACTIONS; const [state, dispatch] = useReducer(reducer, getDefaultState(actions)); const [isBackdropVisible, setBackdropVisible] = useState(false); diff --git a/plugins/cost-insights/src/utils/loading.ts b/plugins/cost-insights/src/utils/loading.ts index 4652603926..40101051b8 100644 --- a/plugins/cost-insights/src/utils/loading.ts +++ b/plugins/cost-insights/src/utils/loading.ts @@ -21,11 +21,13 @@ export enum DefaultLoadingAction { LastCompleteBillingDate = 'billing-date', CostInsightsInitial = 'cost-insights-initial', CostInsightsPage = 'cost-insights-page', + CostInsightsProducts = 'cost-insights-products', } export const INITIAL_LOADING_ACTIONS = [ DefaultLoadingAction.UserGroups, DefaultLoadingAction.CostInsightsInitial, + DefaultLoadingAction.CostInsightsProducts, ]; export const getDefaultState = (loadingActions: string[]): Loading => { @@ -54,11 +56,3 @@ export const getResetStateWithoutInitial = ( return { ...defaultState, [action]: loadingActionState }; }, {}); }; - -export function getLoadingActions(products: string[]): string[] { - return ([ - DefaultLoadingAction.UserGroups, - DefaultLoadingAction.CostInsightsInitial, - DefaultLoadingAction.CostInsightsPage, - ] as string[]).concat(products); -} diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 708ce733dd..e40e5e1ddc 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -145,14 +145,14 @@ export const MockProducts: Product[] = Object.keys(MockProductTypes).map( })), ); -export const MockLoadingActions = ([ +export const MockDefaultLoadingActions = ([ DefaultLoadingAction.UserGroups, DefaultLoadingAction.CostInsightsInitial, DefaultLoadingAction.CostInsightsPage, ] as string[]).concat(MockProducts.map(product => product.kind)); export const mockDefaultLoadingState = getDefaultLoadingState( - MockLoadingActions, + MockDefaultLoadingActions, ); export const MockComputeEngine = findAlways(