diff --git a/.changeset/blue-donkeys-exercise.md b/.changeset/blue-donkeys-exercise.md
new file mode 100644
index 0000000000..4f714e3c97
--- /dev/null
+++ b/.changeset/blue-donkeys-exercise.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': minor
+---
+
+Add client side paging for catalog table
diff --git a/.changeset/great-apples-flash.md b/.changeset/great-apples-flash.md
new file mode 100644
index 0000000000..78ad89dee0
--- /dev/null
+++ b/.changeset/great-apples-flash.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Fix CodeOwnersProcessor to handle non team users
diff --git a/.changeset/selfish-mugs-itch.md b/.changeset/selfish-mugs-itch.md
new file mode 100644
index 0000000000..39ed236352
--- /dev/null
+++ b/.changeset/selfish-mugs-itch.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-cost-insights': minor
+---
+
+Remove product filters from query parameters
diff --git a/app-config.yaml b/app-config.yaml
index 715d564feb..6e49e765cd 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -146,6 +146,9 @@ catalog:
# Backstage example templates
- type: url
target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/all-templates.yaml
+ # Backstage example groups and users
+ - type: url
+ target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml
scaffolder:
github:
diff --git a/packages/catalog-model/examples/acme-corp.yaml b/packages/catalog-model/examples/acme-corp.yaml
new file mode 100644
index 0000000000..7e5c5ebc61
--- /dev/null
+++ b/packages/catalog-model/examples/acme-corp.yaml
@@ -0,0 +1,9 @@
+apiVersion: backstage.io/v1alpha1
+kind: Location
+metadata:
+ name: acme-corp
+ description: A collection of all Backstage example Groups
+spec:
+ type: github
+ targets:
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml
diff --git a/packages/catalog-model/examples/acme/backstage-group.yaml b/packages/catalog-model/examples/acme/backstage-group.yaml
new file mode 100644
index 0000000000..f992d155bc
--- /dev/null
+++ b/packages/catalog-model/examples/acme/backstage-group.yaml
@@ -0,0 +1,11 @@
+apiVersion: backstage.io/v1alpha1
+kind: Group
+metadata:
+ name: backstage
+ description: The backstage sub-department
+spec:
+ type: sub-department
+ parent: infrastructure
+ ancestors: [infrastructure, acme-corp]
+ children: [team-a, team-b]
+ descendants: [team-a, team-b]
diff --git a/packages/catalog-model/examples/acme/boxoffice-group.yaml b/packages/catalog-model/examples/acme/boxoffice-group.yaml
new file mode 100644
index 0000000000..0be1fe58cb
--- /dev/null
+++ b/packages/catalog-model/examples/acme/boxoffice-group.yaml
@@ -0,0 +1,11 @@
+apiVersion: backstage.io/v1alpha1
+kind: Group
+metadata:
+ name: boxoffice
+ description: The boxoffice sub-department
+spec:
+ type: sub-department
+ parent: infrastructure
+ ancestors: [infrastructure, acme-corp]
+ children: [team-c, team-d]
+ descendants: [team-c, team-d]
diff --git a/packages/catalog-model/examples/acme/infrastructure-group.yaml b/packages/catalog-model/examples/acme/infrastructure-group.yaml
new file mode 100644
index 0000000000..2341782944
--- /dev/null
+++ b/packages/catalog-model/examples/acme/infrastructure-group.yaml
@@ -0,0 +1,11 @@
+apiVersion: backstage.io/v1alpha1
+kind: Group
+metadata:
+ name: infrastructure
+ description: The infra department
+spec:
+ type: department
+ parent: acme-corp
+ ancestors: [acme-corp]
+ children: [backstage, boxoffice]
+ descendants: [backstage, boxoffice, team-a, team-b, team-c, team-d]
diff --git a/packages/catalog-model/examples/acme/org.yaml b/packages/catalog-model/examples/acme/org.yaml
new file mode 100644
index 0000000000..2d6a6a2666
--- /dev/null
+++ b/packages/catalog-model/examples/acme/org.yaml
@@ -0,0 +1,27 @@
+apiVersion: backstage.io/v1alpha1
+kind: Group
+metadata:
+ name: acme-corp
+ description: The acme-corp organization
+spec:
+ type: organization
+ ancestors: []
+ children: [infrastructure]
+ descendants:
+ [infrastructure, backstage, boxoffice, team-a, team-b, team-c, team-d]
+---
+apiVersion: backstage.io/v1alpha1
+kind: Location
+metadata:
+ name: example-groups
+ description: A collection of all Backstage example Groups
+spec:
+ type: github
+ targets:
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/acme/infrastructure-group.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/acme/boxoffice-group.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/acme/backstage-group.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/acme/team-a-group.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/acme/team-b-group.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/acme/team-c-group.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/acme/team-d-group.yaml
diff --git a/packages/catalog-model/examples/acme/team-a-group.yaml b/packages/catalog-model/examples/acme/team-a-group.yaml
new file mode 100644
index 0000000000..dacc4c6c3c
--- /dev/null
+++ b/packages/catalog-model/examples/acme/team-a-group.yaml
@@ -0,0 +1,44 @@
+apiVersion: backstage.io/v1alpha1
+kind: Group
+metadata:
+ name: team-a
+ description: Team A
+spec:
+ type: team
+ parent: backstage
+ ancestors: [backstage, infrastructure, acme-corp]
+ children: []
+ descendants: []
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: breanna.davison
+spec:
+ profile:
+ displayName: Breanna Davison
+ email: breanna-davison@example.com
+ picture: https://example.com/staff/breanna.jpeg
+ memberOf: [team-a]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: janelle.dawe
+spec:
+ profile:
+ displayName: Janelle Dawe
+ email: janelle-dawe@example.com
+ picture: https://example.com/staff/janelle.jpeg
+ memberOf: [team-a]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: nigel.manning
+spec:
+ profile:
+ displayName: Nigel Manning
+ email: nigel-manning@example.com
+ picture: https://example.com/staff/nigel.jpeg
+ memberOf: [team-a]
diff --git a/packages/catalog-model/examples/acme/team-b-group.yaml b/packages/catalog-model/examples/acme/team-b-group.yaml
new file mode 100644
index 0000000000..00e9e41d80
--- /dev/null
+++ b/packages/catalog-model/examples/acme/team-b-group.yaml
@@ -0,0 +1,66 @@
+apiVersion: backstage.io/v1alpha1
+kind: Group
+metadata:
+ name: team-b
+ description: Team B
+spec:
+ type: team
+ parent: backstage
+ ancestors: [backstage, infrastructure, acme-corp]
+ children: []
+ descendants: []
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: amelia.park
+spec:
+ profile:
+ displayName: Amelia Park
+ email: amelia-park@example.com
+ picture: https://example.com/staff/amelia.jpeg
+ memberOf: [team-b]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: colette.brock
+spec:
+ profile:
+ displayName: Colette Brock
+ email: colette-brock@example.com
+ picture: https://example.com/staff/colette.jpeg
+ memberOf: [team-b]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: jenny.doe
+spec:
+ profile:
+ displayName: Jenny Doe
+ email: jenny-doe@example.com
+ picture: https://example.com/staff/jenny.jpeg
+ memberOf: [team-b]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: jonathon.page
+spec:
+ profile:
+ displayName: Jonathon Page
+ email: jonathon-page@example.com
+ picture: https://example.com/staff/jonathon.jpeg
+ memberOf: [team-b]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: justine.barrow
+spec:
+ profile:
+ displayName: Justine Barrow
+ email: justine-barrow@example.com
+ picture: https://example.com/staff/justine.jpeg
+ memberOf: [team-b]
diff --git a/packages/catalog-model/examples/acme/team-c-group.yaml b/packages/catalog-model/examples/acme/team-c-group.yaml
new file mode 100644
index 0000000000..0f97f69cb3
--- /dev/null
+++ b/packages/catalog-model/examples/acme/team-c-group.yaml
@@ -0,0 +1,66 @@
+apiVersion: backstage.io/v1alpha1
+kind: Group
+metadata:
+ name: team-c
+ description: Team C
+spec:
+ type: team
+ parent: boxoffice
+ ancestors: [boxoffice, infrastructure, acme-corp]
+ children: []
+ descendants: []
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: calum.leavy
+spec:
+ profile:
+ displayName: Calum Leavy
+ email: calum-leavy@example.com
+ picture: https://example.com/staff/calum.jpeg
+ memberOf: [team-c]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: frank.tiernan
+spec:
+ profile:
+ displayName: Frank Tiernan
+ email: frank-tiernan@example.com
+ picture: https://example.com/staff/frank.jpeg
+ memberOf: [team-c]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: peadar.macmahon
+spec:
+ profile:
+ displayName: Peadar MacMahon
+ email: peadar-macmahon@example.com
+ picture: https://example.com/staff/peadar.jpeg
+ memberOf: [team-c]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: sarah.gilroy
+spec:
+ profile:
+ displayName: Sarah Gilroy
+ email: sarah-gilroy@example.com
+ picture: https://example.com/staff/sarah.jpeg
+ memberOf: [team-c]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: tara.macgovern
+spec:
+ profile:
+ displayName: Tara MacGovern
+ email: tara-macgovern@example.com
+ picture: https://example.com/staff/tara.jpeg
+ memberOf: [team-c]
diff --git a/packages/catalog-model/examples/acme/team-d-group.yaml b/packages/catalog-model/examples/acme/team-d-group.yaml
new file mode 100644
index 0000000000..5a1a53a2e6
--- /dev/null
+++ b/packages/catalog-model/examples/acme/team-d-group.yaml
@@ -0,0 +1,33 @@
+apiVersion: backstage.io/v1alpha1
+kind: Group
+metadata:
+ name: team-d
+ description: Team D
+spec:
+ type: team
+ parent: boxoffice
+ ancestors: [boxoffice, infrastructure, acme-corp]
+ children: []
+ descendants: []
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: eva.macdowell
+spec:
+ profile:
+ displayName: Eva MacDowell
+ email: eva-macdowell@example.com
+ picture: https://example.com/staff/eva.jpeg
+ memberOf: [team-d]
+---
+apiVersion: backstage.io/v1alpha1
+kind: User
+metadata:
+ name: lucy.sheehan
+spec:
+ profile:
+ displayName: Lucy Sheehan
+ email: lucy-sheehan@example.com
+ picture: https://example.com/staff/lucy.jpeg
+ memberOf: [team-d]
diff --git a/packages/catalog-model/examples/all-apis.yaml b/packages/catalog-model/examples/all-apis.yaml
index 5e8937f82b..64586fdddd 100644
--- a/packages/catalog-model/examples/all-apis.yaml
+++ b/packages/catalog-model/examples/all-apis.yaml
@@ -7,6 +7,7 @@ spec:
type: github
targets:
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/apis/hello-world-api.yaml
- - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/apis/streetlights-api.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/apis/petstore-api.yaml
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/apis/spotify-api.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/apis/streetlights-api.yaml
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/apis/swapi-graphql.yaml
diff --git a/packages/catalog-model/examples/all-components.yaml b/packages/catalog-model/examples/all-components.yaml
index 902b40e008..bef38df0b1 100644
--- a/packages/catalog-model/examples/all-components.yaml
+++ b/packages/catalog-model/examples/all-components.yaml
@@ -7,6 +7,7 @@ spec:
type: github
targets:
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/components/artist-lookup-component.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/components/petstore-component.yaml
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/components/playback-order-component.yaml
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/components/podcast-api-component.yaml
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/components/queue-proxy-component.yaml
diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts
index 622af9ea59..e65714bcfd 100644
--- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts
@@ -123,6 +123,10 @@ describe('CodeOwnersProcessor', () => {
});
describe('normalizeCodeOwner', () => {
+ it('should remove the @ symbol', () => {
+ expect(normalizeCodeOwner('@yoda')).toBe('yoda');
+ });
+
it('should remove org from org/team format', () => {
expect(normalizeCodeOwner('@acme/foo')).toBe('foo');
});
diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts
index bc78b6e34a..f186a9425c 100644
--- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts
@@ -127,6 +127,8 @@ export function findPrimaryCodeOwner(
export function normalizeCodeOwner(owner: string) {
if (owner.match(/^@.*\/.*/)) {
return owner.split('/')[1];
+ } else if (owner.match(/^@.*/)) {
+ return owner.substring(1);
} else if (owner.match(/^.*@.*\..*$/)) {
return owner.split('@')[0];
}
diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
index 266d5fa843..3214ea3385 100644
--- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx
+++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
@@ -26,6 +26,7 @@ import {
makeStyles,
Typography,
} from '@material-ui/core';
+import BrightnessAutoIcon from '@material-ui/icons/BrightnessAuto';
import DocsIcon from '@material-ui/icons/Description';
import EditIcon from '@material-ui/icons/Edit';
import GitHubIcon from '@material-ui/icons/GitHub';
@@ -120,6 +121,14 @@ export function AboutCard({ entity, variant }: AboutCardProps) {
entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE
}/${entity.kind}/${entity.metadata.name}`}
/>
+ }
+ href={`./${
+ entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE
+ }:${entity.metadata.name}/api`}
+ />
}
/>
diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx
index f5885145b3..4e63c6099b 100644
--- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx
+++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx
@@ -155,7 +155,8 @@ export const CatalogTable = ({
isLoading={loading}
columns={columns}
options={{
- paging: false,
+ paging: true,
+ pageSize: 10,
actionsColumnIndex: -1,
loadingType: 'linear',
showEmptyDataSourceMessage: !loading,
diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json
index 3d548402be..e79c4ab962 100644
--- a/plugins/cost-insights/package.json
+++ b/plugins/cost-insights/package.json
@@ -40,7 +40,8 @@
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
- "recharts": "^1.8.5"
+ "recharts": "^1.8.5",
+ "yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.25",
@@ -50,6 +51,7 @@
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
+ "@types/yup": "^0.29.8",
"@types/recharts": "^1.8.14",
"msw": "^0.21.2",
"cross-fetch": "^3.0.6",
diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx
index c9790a5bfa..13bb990740 100644
--- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx
+++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx
@@ -51,7 +51,7 @@ const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => {
const dispatchLoadingProduct = useCallback(dispatchLoading, [product.kind]);
const amount = resource?.entities?.length || 0;
- const hasCostsWithinTimeframe = resource?.change && amount;
+ const hasCostsWithinTimeframe = resource?.change && !!amount;
const subheader = amount
? `${amount} ${pluralOf(amount, 'entity', 'entities')}, sorted by cost`
diff --git a/plugins/cost-insights/src/hooks/index.ts b/plugins/cost-insights/src/hooks/index.ts
index 7065bcf91b..8998a10e2c 100644
--- a/plugins/cost-insights/src/hooks/index.ts
+++ b/plugins/cost-insights/src/hooks/index.ts
@@ -20,5 +20,4 @@ export * from './useFilters';
export * from './useCurrency';
export * from './useGroups';
export * from './useLoading';
-export * from './useQueryParams';
export * from './useScroll';
diff --git a/plugins/cost-insights/src/hooks/useFilters.tsx b/plugins/cost-insights/src/hooks/useFilters.tsx
index 7dcf8ddb26..22ef735186 100644
--- a/plugins/cost-insights/src/hooks/useFilters.tsx
+++ b/plugins/cost-insights/src/hooks/useFilters.tsx
@@ -16,114 +16,98 @@
import React, {
Dispatch,
- ReactNode,
+ PropsWithChildren,
SetStateAction,
useContext,
useEffect,
- useRef,
useState,
} from 'react';
-import {
- getDefaultPageFilters,
- PageFilters,
- ProductFilters,
- Duration,
- Group,
-} from '../types';
+import { Alert } from '@material-ui/lab';
+import { Maybe, PageFilters, ProductFilters } from '../types';
import { useLocation, useNavigate } from 'react-router-dom';
-import { useQueryParams } from './useQueryParams';
-import { stringify } from '../utils/history';
+import {
+ stringify,
+ validate,
+ getInitialPageState,
+ getInitialProductState,
+} from '../utils/history';
import { useGroups } from './useGroups';
import { useConfig } from './useConfig';
-const getInitialPageState = (
- groups: Group[],
- queryParams?: Partial,
-) => {
- // The group is written initially to queryParams as null, since user groups are asynchronously
- // loaded. We preserve nulls in queryParams for other parameters where null is meaningful; for
- // group, avoid overwriting the default with null after groups are loaded.
- const { group, ...otherParams } = queryParams || {};
- return {
- ...getDefaultPageFilters(groups),
- ...otherParams,
- ...(group ? { group: group } : {}),
- };
-};
-
export type FilterContextProps = {
pageFilters: PageFilters;
productFilters: ProductFilters;
- setPageFilters: Dispatch>;
- setProductFilters: Dispatch>;
+ setPageFilters: Dispatch>>;
+ setProductFilters: Dispatch>>;
};
export type MapFiltersToProps = (props: FilterContextProps) => T;
-export type FilterProviderProps = {
- children: ReactNode;
-};
-
export const FilterContext = React.createContext<
FilterContextProps | undefined
>(undefined);
-export const FilterProvider = ({ children }: FilterProviderProps) => {
+export const FilterProvider = ({ children }: PropsWithChildren<{}>) => {
const config = useConfig();
const navigate = useNavigate();
const location = useLocation();
- const queryParams = useQueryParams();
- const qsRef = useRef('');
const groups = useGroups();
-
- const defaultProductFilters = config.products.map(product => ({
- productType: product.kind,
- duration: Duration.P1M,
- }));
-
- const getInitialProductState = (productFilters?: ProductFilters) => {
- if (!productFilters) return defaultProductFilters;
- return defaultProductFilters.map(product => {
- return (
- productFilters.find(
- param => param.productType === product.productType,
- ) || product
- );
- });
- };
-
- const [productFilters, setProductFilters] = useState(
- getInitialProductState(queryParams.productFilters),
- );
- const [pageFilters, setPageFilters] = useState(
- getInitialPageState(groups, queryParams.pageFilters),
+ const [error, setError] = useState>(null);
+ const [pageFilters, setPageFilters] = useState>(null);
+ const [productFilters, setProductFilters] = useState>(
+ null,
);
- // TODO: Figure out why pageFilters doesn't get updated by the above when groups are loaded.
useEffect(() => {
- const initialState = getInitialPageState(groups, queryParams.pageFilters);
- const defaultMetric = config.metrics.find(m => m.default);
- setPageFilters({ ...initialState, metric: defaultMetric?.kind ?? null });
+ async function setPageFiltersFromLocation() {
+ try {
+ // strip extraneous parameters, validate and transform
+ const queryParams = await validate(location.search);
+ const defaultMetric = config.metrics.find(m => m.default)?.kind ?? null;
+
+ // Group or project parameters should override defaults
+ const initialPageState = getInitialPageState(groups, queryParams);
+ const initialProductState = getInitialProductState(config);
+
+ setProductFilters(initialProductState);
+ setPageFilters({ ...initialPageState, metric: defaultMetric });
+ } catch (e) {
+ setError(e);
+ }
+ }
+
+ setPageFiltersFromLocation();
}, [groups]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
- const queryString = stringify({ ...pageFilters, products: productFilters });
- if (queryString === qsRef.current) return;
- qsRef.current = queryString;
- // TODO Remove workaround once issue is resolved in react-router
- // (https://github.com/ReactTraining/react-router/issues/7496)
- // navigate({ ...location, search: queryString });
- navigate({ ...location, search: `?${queryString}` });
- }, [pageFilters, productFilters]); // eslint-disable-line react-hooks/exhaustive-deps
+ function setLocationFromPageFilters(filters: PageFilters) {
+ const queryString = stringify({
+ group: filters.group,
+ ...(filters.project ? { project: filters.project } : {}),
+ });
+ // TODO Remove workaround once issue is resolved in react-router
+ // (https://github.com/ReactTraining/react-router/issues/7496)
+ // navigate({ ...location, search: queryString });
+ navigate({ ...location, search: `?${queryString}` });
+ }
+
+ if (pageFilters) {
+ setLocationFromPageFilters(pageFilters);
+ }
+ }, [pageFilters]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ if (error) {
+ return {error.message};
+ }
+
+ // Wait for filters to load
+ if (!pageFilters || !productFilters) {
+ return null;
+ }
return (
{children}
@@ -132,17 +116,7 @@ export const FilterProvider = ({ children }: FilterProviderProps) => {
export function useFilters(mapFiltersToProps: MapFiltersToProps): T {
const context = useContext(FilterContext);
-
- if (!context) {
- assertNever();
- }
-
- return mapFiltersToProps({
- pageFilters: context.pageFilters,
- productFilters: context.productFilters,
- setPageFilters: context.setPageFilters,
- setProductFilters: context.setProductFilters,
- });
+ return context ? mapFiltersToProps(context) : assertNever();
}
function assertNever(): never {
diff --git a/plugins/cost-insights/src/hooks/useGroups.tsx b/plugins/cost-insights/src/hooks/useGroups.tsx
index 02c32e7920..8f11c88e60 100644
--- a/plugins/cost-insights/src/hooks/useGroups.tsx
+++ b/plugins/cost-insights/src/hooks/useGroups.tsx
@@ -14,11 +14,17 @@
* limitations under the License.
*/
-import React, { ReactNode, useContext, useEffect, useState } from 'react';
+import React, {
+ PropsWithChildren,
+ useContext,
+ useEffect,
+ useState,
+} from 'react';
+import { Alert } from '@material-ui/lab';
import { useApi, identityApiRef } from '@backstage/core';
import { costInsightsApiRef } from '../api';
import { MapLoadingToProps, useLoading } from './useLoading';
-import { DefaultLoadingAction, Group } from '../types';
+import { DefaultLoadingAction, Group, Maybe } from '../types';
type GroupsProviderLoadingProps = {
dispatchLoadingGroups: (isLoading: boolean) => void;
@@ -35,33 +41,41 @@ type GroupsContextProps = {
groups: Group[];
};
-export type GroupsProviderProps = {
- children: ReactNode;
-};
+export const GroupsContext = React.createContext<
+ GroupsContextProps | undefined
+>(undefined);
-export const GroupsContext = React.createContext({
- groups: [],
-});
-
-export const GroupsProvider = ({ children }: GroupsProviderProps) => {
+export const GroupsProvider = ({ children }: PropsWithChildren<{}>) => {
const userId = useApi(identityApiRef).getUserId();
const client = useApi(costInsightsApiRef);
+ const [error, setError] = useState>(null);
const { dispatchLoadingGroups } = useLoading(mapLoadingToProps);
- const [groups, setGroups] = useState([]);
+ const [groups, setGroups] = useState>(null);
useEffect(() => {
dispatchLoadingGroups(true);
async function getUserGroups() {
- const g = await client.getUserGroups(userId);
- setGroups(g);
- dispatchLoadingGroups(false);
+ try {
+ const g = await client.getUserGroups(userId);
+ setGroups(g);
+ } catch (e) {
+ setError(e);
+ } finally {
+ dispatchLoadingGroups(false);
+ }
}
getUserGroups();
}, [userId, client]); // eslint-disable-line react-hooks/exhaustive-deps
+ if (error) {
+ return {error.message};
+ }
+
+ if (!groups) return null;
+
return (
{children}
@@ -70,13 +84,8 @@ export const GroupsProvider = ({ children }: GroupsProviderProps) => {
};
export function useGroups(): Group[] {
- const { groups } = useContext(GroupsContext);
-
- if (!groups) {
- assertNever();
- }
-
- return groups;
+ const context = useContext(GroupsContext);
+ return context ? context.groups : assertNever();
}
function assertNever(): never {
diff --git a/plugins/cost-insights/src/hooks/useQueryParams.tsx b/plugins/cost-insights/src/hooks/useQueryParams.tsx
deleted file mode 100644
index 1e6ce8f0fd..0000000000
--- a/plugins/cost-insights/src/hooks/useQueryParams.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 { useLocation } from 'react-router-dom';
-import { Location } from 'history';
-import { PageFilters, ProductFilters } from '../types';
-import { parse } from '../utils/history';
-
-export type FilterParams = {
- pageFilters?: Partial;
- productFilters?: ProductFilters;
-};
-
-export function useQueryParams(): FilterParams {
- const location: Location = useLocation();
- const { products: productFilters, ...pageFilters } = parse(location.search);
-
- return {
- productFilters: productFilters,
- pageFilters: pageFilters,
- };
-}
diff --git a/plugins/cost-insights/src/types/Filters.ts b/plugins/cost-insights/src/types/Filters.ts
index f90b9b4c78..b1fbbb2848 100644
--- a/plugins/cost-insights/src/types/Filters.ts
+++ b/plugins/cost-insights/src/types/Filters.ts
@@ -27,8 +27,6 @@ export interface PageFilters {
export type ProductFilters = Array;
-export type QueryParams = PageFilters & { products: ProductFilters };
-
export interface ProductPeriod {
duration: Duration;
productType: string;
diff --git a/plugins/cost-insights/src/utils/history.test.ts b/plugins/cost-insights/src/utils/history.test.ts
new file mode 100644
index 0000000000..e4310e13c6
--- /dev/null
+++ b/plugins/cost-insights/src/utils/history.test.ts
@@ -0,0 +1,80 @@
+/*
+ * 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 { validate, getInitialPageState } from './history';
+
+describe('getInitialPageState', () => {
+ describe('groups', () => {
+ it('should set defaults if params or group is not provided', () => {
+ const initialState = getInitialPageState([]);
+ expect(initialState.group).toBe(null);
+ });
+
+ it('should set defaults if a group is fetched but no group is present on query params', () => {
+ const initialState = getInitialPageState([{ id: 'group' }]);
+ expect(initialState.group).toMatch(/group/);
+ });
+
+ it('group param should always override fetched group', () => {
+ const initialState = getInitialPageState(
+ [{ id: 'group' }, { id: 'second-group' }],
+ { group: 'other-group' },
+ );
+ expect(initialState.group).toMatch(/other-group/);
+ });
+
+ it('first group should be set as default group if user belongs to multiple groups', () => {
+ const initialState = getInitialPageState([
+ { id: 'group' },
+ { id: 'other-group' },
+ ]);
+ expect(initialState.group).toMatch(/group/);
+ });
+ });
+
+ describe('projects', () => {
+ it("should set defaults if project param doesn't exist", () => {
+ const initialState = getInitialPageState([], {});
+ expect(initialState.project).toBeNull();
+ });
+
+ it('should override defaults if project param exists', () => {
+ const initialState = getInitialPageState([], { project: 'some-project' });
+ expect(initialState.project).toMatch(/some-project/);
+ });
+ });
+});
+
+describe.each`
+ params | expected
+ ${''} | ${{}}
+ ${'?foo=bar'} | ${{}}
+ ${'?project'} | ${{ project: null }}
+ ${'?group=some-group'} | ${{ group: 'some-group' }}
+ ${'?group=some-group&project'} | ${{ group: 'some-group', project: null }}
+ ${'?group=some-group&project=some-project'} | ${{ group: 'some-group', project: 'some-project' }}
+ ${'?group=some-group&project=some-project&foo=bar'} | ${{ group: 'some-group', project: 'some-project' }}
+`('validate', ({ params, expected }) => {
+ it(`should validate ${params}`, async () => {
+ const pageFilters = await validate(params);
+ expect(pageFilters).toMatchObject(expected);
+ });
+});
+
+describe('invalidate', () => {
+ it("should throw an error if param values don't match schema", async () => {
+ await expect(validate('?group')).rejects.toThrowError();
+ });
+});
diff --git a/plugins/cost-insights/src/utils/history.ts b/plugins/cost-insights/src/utils/history.ts
index 71e8e2c219..90df574acf 100644
--- a/plugins/cost-insights/src/utils/history.ts
+++ b/plugins/cost-insights/src/utils/history.ts
@@ -15,9 +15,44 @@
*/
import qs from 'qs';
-import { QueryParams } from '../types';
+import * as yup from 'yup';
+import { Group, PageFilters, getDefaultPageFilters, Duration } from '../types';
+import { ConfigContextProps } from '../hooks/useConfig';
-export const stringify = (queryParams: Partial) =>
+const schema = yup
+ .object()
+ .shape({
+ group: yup.string(),
+ project: yup.string().nullable(),
+ })
+ .required();
+
+export const stringify = (queryParams: Partial) =>
qs.stringify(queryParams, { strictNullHandling: true });
-export const parse = (queryString: string): Partial =>
+
+export const parse = (queryString: string): Partial =>
qs.parse(queryString, { ignoreQueryPrefix: true, strictNullHandling: true });
+
+export const validate = (queryString: string): Promise => {
+ return schema.validate(parse(queryString), {
+ stripUnknown: true,
+ strict: true,
+ }) as Promise;
+};
+
+export const getInitialPageState = (
+ groups: Group[],
+ queryParams: Partial = {},
+) => {
+ return {
+ ...getDefaultPageFilters(groups),
+ ...(queryParams.project ? { project: queryParams.project } : {}),
+ ...(queryParams.group ? { group: queryParams.group } : {}),
+ };
+};
+
+export const getInitialProductState = (config: ConfigContextProps) =>
+ config.products.map(product => ({
+ productType: product.kind,
+ duration: Duration.P30D,
+ }));
diff --git a/plugins/cost-insights/src/utils/tests.tsx b/plugins/cost-insights/src/utils/tests.tsx
index 807232555b..552c524afe 100644
--- a/plugins/cost-insights/src/utils/tests.tsx
+++ b/plugins/cost-insights/src/utils/tests.tsx
@@ -16,6 +16,7 @@
import React, { Dispatch, ReactNode, SetStateAction } from 'react';
import {
getDefaultPageFilters,
+ Maybe,
PageFilters,
ProductFilters,
Group,
@@ -29,8 +30,8 @@ import { MockProductFilters } from './mockData';
export const MockGroups: Group[] = [{ id: 'tech' }, { id: 'mock-group' }];
type MockFilterProviderProps = {
- setPageFilters: Dispatch>;
- setProductFilters: Dispatch>;
+ setPageFilters: Dispatch>>;
+ setProductFilters: Dispatch>>;
children: ReactNode;
};
diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json
index a286157e08..60c7c8f084 100644
--- a/plugins/techdocs-backend/package.json
+++ b/plugins/techdocs-backend/package.json
@@ -26,7 +26,6 @@
"@types/dockerode": "^2.5.34",
"@types/express": "^4.17.6",
"command-exists-promise": "^2.0.2",
- "default-branch": "^1.0.8",
"dockerode": "^3.2.1",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
diff --git a/plugins/techdocs-backend/src/default-branch.ts b/plugins/techdocs-backend/src/default-branch.ts
index 6ed12348ac..2997a2acbf 100644
--- a/plugins/techdocs-backend/src/default-branch.ts
+++ b/plugins/techdocs-backend/src/default-branch.ts
@@ -17,6 +17,13 @@ import fetch from 'cross-fetch';
import parseGitUrl from 'git-url-parse';
import { Config } from '@backstage/config';
import { getRootLogger, loadBackendConfig } from '@backstage/backend-common';
+import {
+ getAzureHostToken,
+ getGitHost,
+ getGithubHostToken,
+ getGitlabHostToken,
+ getGitRepoType,
+} from './git-auth';
interface IGitlabBranch {
name: string;
@@ -73,15 +80,12 @@ function getAzureApiUrl(url: string): URL {
);
}
-function getGithubRequestOptions(config: Config): RequestInit {
+function getGithubRequestOptions(config: Config, host: string): RequestInit {
const headers: HeadersInit = {
Accept: 'application/vnd.github.v3.raw',
};
- const token =
- config.getOptionalString('catalog.processors.github.privateToken') ??
- config.getOptionalString('catalog.processors.githubApi.privateToken') ??
- process.env.GITHUB_TOKEN;
+ const token = getGithubHostToken(config, host);
if (token) {
headers.Authorization = `token ${token}`;
@@ -92,16 +96,12 @@ function getGithubRequestOptions(config: Config): RequestInit {
};
}
-function getGitlabRequestOptions(config: Config): RequestInit {
+function getGitlabRequestOptions(config: Config, host: string): RequestInit {
const headers: HeadersInit = {
'PRIVATE-TOKEN': '',
};
- const token =
- config.getOptionalString('catalog.processors.gitlab.privateToken') ??
- config.getOptionalString('catalog.processors.gitlabApi.privateToken') ??
- process.env.GITLAB_TOKEN;
-
+ const token = getGitlabHostToken(config, host);
if (token) {
headers['PRIVATE-TOKEN'] = token;
}
@@ -111,12 +111,10 @@ function getGitlabRequestOptions(config: Config): RequestInit {
};
}
-function getAzureRequestOptions(config: Config): RequestInit {
+function getAzureRequestOptions(config: Config, host: string): RequestInit {
const headers: HeadersInit = {};
- const token =
- config.getOptionalString('catalog.processors.azureApi.privateToken') ??
- process.env.AZURE_TOKEN;
+ const token = getAzureHostToken(config, host);
if (token !== '') {
headers.Authorization = `Basic ${Buffer.from(`:${token}`, 'utf8').toString(
@@ -136,7 +134,8 @@ async function getGithubDefaultBranch(
config: Config,
): Promise {
const path = getGithubApiUrl(config, repositoryUrl).toString();
- const options = getGithubRequestOptions(config);
+ const host = getGitHost(repositoryUrl);
+ const options = getGithubRequestOptions(config, host);
try {
const raw = await fetch(path, options);
@@ -165,7 +164,8 @@ async function getGitlabDefaultBranch(
): Promise {
const path = getGitlabApiUrl(repositoryUrl).toString();
- const options = getGitlabRequestOptions(config);
+ const gitlabHost = getGitHost(repositoryUrl);
+ const options = getGitlabRequestOptions(config, gitlabHost);
try {
const raw = await fetch(path, options);
@@ -196,8 +196,8 @@ async function getAzureDefaultBranch(
config: Config,
): Promise {
const path = getAzureApiUrl(repositoryUrl).toString();
-
- const options = getAzureRequestOptions(config);
+ const host = getGitHost(repositoryUrl);
+ const options = getAzureRequestOptions(config, host);
try {
const urlResponse = await fetch(path, options);
@@ -232,14 +232,7 @@ export const getDefaultBranch = async (
): Promise => {
// TODO(Rugvip): Config should not be loaded here, pass it in instead
const config = await loadBackendConfig({ logger: getRootLogger() });
- const typeMapping = [
- { url: /github*/g, type: 'github' },
- { url: /gitlab*/g, type: 'gitlab' },
- { url: /azure*/g, type: 'azure/api' },
- ];
-
- const type = typeMapping.filter(item => item.url.test(repositoryUrl))[0]
- ?.type;
+ const type = getGitRepoType(repositoryUrl);
try {
switch (type) {
diff --git a/plugins/techdocs-backend/src/git-auth.ts b/plugins/techdocs-backend/src/git-auth.ts
new file mode 100644
index 0000000000..d77596929f
--- /dev/null
+++ b/plugins/techdocs-backend/src/git-auth.ts
@@ -0,0 +1,116 @@
+/*
+ * 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 parseGitUrl from 'git-url-parse';
+import { Config } from '@backstage/config';
+import { getRootLogger, loadBackendConfig } from '@backstage/backend-common';
+
+export function getGitHost(url: string): string {
+ const { resource } = parseGitUrl(url);
+ return resource;
+}
+
+export function getGitRepoType(url: string): string {
+ const typeMapping = [
+ { url: /github*/g, type: 'github' },
+ { url: /gitlab*/g, type: 'gitlab' },
+ { url: /azure*/g, type: 'azure/api' },
+ ];
+
+ const type = typeMapping.filter(item => item.url.test(url))[0]?.type;
+
+ return type;
+}
+
+export function getGithubHostToken(
+ config: Config,
+ host: string,
+): string | undefined {
+ const providerConfigs =
+ config.getOptionalConfigArray('integrations.github') ?? [];
+
+ const hostConfig = providerConfigs.filter(
+ providerConfig => providerConfig.getOptionalString('host') === host,
+ );
+ const token =
+ hostConfig[0]?.getOptionalString('token') ??
+ config.getOptionalString('catalog.processors.github.privateToken') ??
+ config.getOptionalString('catalog.processors.githubApi.privateToken') ??
+ process.env.GITHUB_TOKEN;
+
+ return token;
+}
+
+export function getGitlabHostToken(
+ config: Config,
+ host: string,
+): string | undefined {
+ const providerConfigs =
+ config.getOptionalConfigArray('integrations.gitlab') ?? [];
+
+ const hostConfig = providerConfigs.filter(
+ providerConfig => providerConfig.getOptionalString('host') === host,
+ );
+ const token =
+ hostConfig[0]?.getOptionalString('token') ??
+ config.getOptionalString('catalog.processors.gitlab.privateToken') ??
+ config.getOptionalString('catalog.processors.gitlabApi.privateToken') ??
+ process.env.GITLAB_TOKEN;
+
+ return token;
+}
+
+export function getAzureHostToken(
+ config: Config,
+ host: string,
+): string | undefined {
+ const providerConfigs =
+ config.getOptionalConfigArray('integrations.azure') ?? [];
+
+ const hostConfig = providerConfigs.filter(
+ providerConfig => providerConfig.getOptionalString('host') === host,
+ );
+ const token =
+ hostConfig[0]?.getOptionalString('token') ??
+ config.getOptionalString('catalog.processors.azureApi.privateToken') ??
+ process.env.AZURE_TOKEN;
+
+ return token;
+}
+
+export const getTokenForGitRepo = async (
+ repositoryUrl: string,
+): Promise => {
+ const config = await loadBackendConfig({ logger: getRootLogger() });
+
+ const host = getGitHost(repositoryUrl);
+ const type = getGitRepoType(repositoryUrl);
+
+ try {
+ switch (type) {
+ case 'github':
+ return getGithubHostToken(config, host);
+ case 'gitlab':
+ return getGitlabHostToken(config, host);
+ case 'azure/api':
+ return getAzureHostToken(config, host);
+
+ default:
+ throw new Error('Failed to get repository type');
+ }
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/plugins/techdocs-backend/src/helpers.ts b/plugins/techdocs-backend/src/helpers.ts
index b2dcd1e8e8..4a21800b07 100644
--- a/plugins/techdocs-backend/src/helpers.ts
+++ b/plugins/techdocs-backend/src/helpers.ts
@@ -20,6 +20,7 @@ import parseGitUrl from 'git-url-parse';
import NodeGit, { Clone, Repository } from 'nodegit';
import fs from 'fs-extra';
import { getDefaultBranch } from './default-branch';
+import { getTokenForGitRepo } from './git-auth';
import { Entity } from '@backstage/catalog-model';
import { InputError } from '@backstage/backend-common';
import { RemoteProtocol } from './techdocs/stages/prepare/types';
@@ -127,11 +128,8 @@ export const checkoutGitRepository = async (
process.env.GITLAB_PRIVATE_TOKEN_USER ||
process.env.AZURE_PRIVATE_TOKEN_USER ||
'';
- const token =
- process.env.GITHUB_TOKEN ||
- process.env.GITLAB_PRIVATE_TOKEN_USER ||
- process.env.AZURE_TOKEN ||
- '';
+
+ const token = await getTokenForGitRepo(repoUrl);
if (fs.existsSync(repositoryTmpPath)) {
try {
@@ -153,7 +151,7 @@ export const checkoutGitRepository = async (
}
}
- if (user && token) {
+ if (token) {
parsedGitLocation.token = `${user}:${token}`;
}
diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.test.ts b/plugins/techdocs/src/reader/transformers/onCssReady.test.ts
index c83a94f35d..67ae38c56b 100644
--- a/plugins/techdocs/src/reader/transformers/onCssReady.test.ts
+++ b/plugins/techdocs/src/reader/transformers/onCssReady.test.ts
@@ -25,14 +25,20 @@ import { onCssReady } from '../transformers';
const docStorageUrl: string =
'https://techdocs-mock-sites.storage.googleapis.com';
-jest.useFakeTimers();
-
const fixture = `
`;
describe('onCssReady', () => {
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
beforeEach(() => {
mockStylesheetEventListener(100);
});
diff --git a/yarn.lock b/yarn.lock
index 524f37b63d..7e15bfdf28 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3830,16 +3830,16 @@
integrity sha512-so8w32ZV42CHWxOEXcBtbNO/hLXFrQNXVmhfzhUI6dVB9cq2xjRaiqu8GjFj8LvKbWpPj+S+KwTIS4aDVWqrFQ==
"@storybook/addon-actions@^6.0.21":
- version "6.0.21"
- resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.0.21.tgz#0de1d109d4b1eb99f644bbe84e74c25cfd2b1b6b"
- integrity sha512-9y3ve+3GK1TsxQ5pxDjhB7E/XJXY+WqcSNlOX8Mb+XbS6AAgpFbkZCw1q8CGzyEUclHsQ6UK2+lo+IRGs4TLpA==
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.0.26.tgz#d0de9e4d78a8f8f5bf8730c04d0b6d1741c29273"
+ integrity sha512-9tWbAqSwzWWVz8zwAndZFusZYjIcRYgZUC0LqC8QlH79DgF3ASjw9y97+w1VTTAzdb6LYnsMuSpX6+8m5hrR4g==
dependencies:
- "@storybook/addons" "6.0.21"
- "@storybook/api" "6.0.21"
- "@storybook/client-api" "6.0.21"
- "@storybook/components" "6.0.21"
- "@storybook/core-events" "6.0.21"
- "@storybook/theming" "6.0.21"
+ "@storybook/addons" "6.0.26"
+ "@storybook/api" "6.0.26"
+ "@storybook/client-api" "6.0.26"
+ "@storybook/components" "6.0.26"
+ "@storybook/core-events" "6.0.26"
+ "@storybook/theming" "6.0.26"
core-js "^3.0.1"
fast-deep-equal "^3.1.1"
global "^4.3.2"
@@ -3892,7 +3892,7 @@
react-syntax-highlighter "^12.2.1"
regenerator-runtime "^0.13.3"
-"@storybook/addons@6.0.21", "@storybook/addons@^6.0.21":
+"@storybook/addons@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.21.tgz#bd5229652102c3aed59b78ef6920ff6b482b4d78"
integrity sha512-yDttNLc3vXqBxwK795ykgzTC6MpvuXDQuF4LHSlHZQe6wsMu1m3fljnbYdafJWdx6cNZwUblU3KYcR11PqhkPg==
@@ -3907,6 +3907,21 @@
global "^4.3.2"
regenerator-runtime "^0.13.3"
+"@storybook/addons@6.0.26", "@storybook/addons@^6.0.21":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.26.tgz#343cbea3eee2d39413b80bc2d66535a7f61488fc"
+ integrity sha512-OhAApFKgsj9an7FLYfHI4cJQuZ4Zm6yoGOpaxhOvKQMw7dXUPsLvbCyw/6dZOLvaFhjJjQiXtbxtZG+UjR8nvA==
+ dependencies:
+ "@storybook/api" "6.0.26"
+ "@storybook/channels" "6.0.26"
+ "@storybook/client-logger" "6.0.26"
+ "@storybook/core-events" "6.0.26"
+ "@storybook/router" "6.0.26"
+ "@storybook/theming" "6.0.26"
+ core-js "^3.0.1"
+ global "^4.3.2"
+ regenerator-runtime "^0.13.3"
+
"@storybook/api@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/api/-/api-6.0.21.tgz#a25a1eb4d07dc43500e03c856db43baba46726f1"
@@ -3933,6 +3948,32 @@
ts-dedent "^1.1.1"
util-deprecate "^1.0.2"
+"@storybook/api@6.0.26":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/api/-/api-6.0.26.tgz#c45222c132eb8bc2e383536adfebbeb7a89867d0"
+ integrity sha512-aszDoz1c6T+eRtTUwWvySoyd3gRXmQxsingD084NnEp4VfFLA5H7VS/0sre0ZvU5GWh8d9COxY0DS2Ry/QSKvw==
+ dependencies:
+ "@reach/router" "^1.3.3"
+ "@storybook/channels" "6.0.26"
+ "@storybook/client-logger" "6.0.26"
+ "@storybook/core-events" "6.0.26"
+ "@storybook/csf" "0.0.1"
+ "@storybook/router" "6.0.26"
+ "@storybook/semver" "^7.3.2"
+ "@storybook/theming" "6.0.26"
+ "@types/reach__router" "^1.3.5"
+ core-js "^3.0.1"
+ fast-deep-equal "^3.1.1"
+ global "^4.3.2"
+ lodash "^4.17.15"
+ memoizerific "^1.11.3"
+ react "^16.8.3"
+ regenerator-runtime "^0.13.3"
+ store2 "^2.7.1"
+ telejson "^5.0.2"
+ ts-dedent "^1.1.1"
+ util-deprecate "^1.0.2"
+
"@storybook/channel-postmessage@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.0.21.tgz#97e8f43c1b66f84c7b8271e447d45d4f66d355d1"
@@ -3946,6 +3987,19 @@
qs "^6.6.0"
telejson "^5.0.2"
+"@storybook/channel-postmessage@6.0.26":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.0.26.tgz#a98a0132d6bdf06741afac2607e9feabe34ab98b"
+ integrity sha512-FT6lC8M5JlNBxPT0rYfmF1yl9mBv04nfYs82TZpp1CzpLxf7wxdCBZ8SSRmvWIVBoNwGZPDhIk5+6JWyDEISBg==
+ dependencies:
+ "@storybook/channels" "6.0.26"
+ "@storybook/client-logger" "6.0.26"
+ "@storybook/core-events" "6.0.26"
+ core-js "^3.0.1"
+ global "^4.3.2"
+ qs "^6.6.0"
+ telejson "^5.0.2"
+
"@storybook/channels@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.21.tgz#bc0951efacbaa5f8827693fba4fe7c2290b5772c"
@@ -3955,6 +4009,15 @@
ts-dedent "^1.1.1"
util-deprecate "^1.0.2"
+"@storybook/channels@6.0.26":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.26.tgz#3e8678b4b40085081257a39b9e85fab13a19943c"
+ integrity sha512-H0iUorayYqS+zfhbjd+cYRzAdRLGLWUeWFu2Aa+oJ4/zeAQNL+DafWboHc567RQ4Vb5KqE5QZoCFskWUUYqJYA==
+ dependencies:
+ core-js "^3.0.1"
+ ts-dedent "^1.1.1"
+ util-deprecate "^1.0.2"
+
"@storybook/client-api@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.0.21.tgz#6a652dea67d219a31d18af0e05b9f17ba6c7c316"
@@ -3978,6 +4041,29 @@
ts-dedent "^1.1.1"
util-deprecate "^1.0.2"
+"@storybook/client-api@6.0.26":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.0.26.tgz#ac9334ba86834e5cb23fc4fb577de60bda66164d"
+ integrity sha512-Qd5wR5b5lio/EchuJMhAmmJAE1pfvnEyu+JnyFGwMZLV9mN9NSspz+YsqbSCCDZsYcP5ewvPEnumIWqmj/wagQ==
+ dependencies:
+ "@storybook/addons" "6.0.26"
+ "@storybook/channel-postmessage" "6.0.26"
+ "@storybook/channels" "6.0.26"
+ "@storybook/client-logger" "6.0.26"
+ "@storybook/core-events" "6.0.26"
+ "@storybook/csf" "0.0.1"
+ "@types/qs" "^6.9.0"
+ "@types/webpack-env" "^1.15.2"
+ core-js "^3.0.1"
+ global "^4.3.2"
+ lodash "^4.17.15"
+ memoizerific "^1.11.3"
+ qs "^6.6.0"
+ stable "^0.1.8"
+ store2 "^2.7.1"
+ ts-dedent "^1.1.1"
+ util-deprecate "^1.0.2"
+
"@storybook/client-logger@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.21.tgz#20369addf9eb79fc0c85a2e0dcb48f5a1a544532"
@@ -3986,6 +4072,14 @@
core-js "^3.0.1"
global "^4.3.2"
+"@storybook/client-logger@6.0.26":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.26.tgz#e3d28bd8dc02ec2c53a9d69773a68189590b746f"
+ integrity sha512-VNoL6/oehVhn3hZi9vrTNT+C/3oAZKV+smfZFnPtsCR/Fq7CKbmsBd0pGPL57f81RU8e8WygwrIlAGJTDSNIjw==
+ dependencies:
+ core-js "^3.0.1"
+ global "^4.3.2"
+
"@storybook/components@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/components/-/components-6.0.21.tgz#2f355370f993e0b7b9062094a03dffc2cdda91db"
@@ -4014,6 +4108,34 @@
react-textarea-autosize "^8.1.1"
ts-dedent "^1.1.1"
+"@storybook/components@6.0.26":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/components/-/components-6.0.26.tgz#e1f6e16aae850a71c9ac7bdd1d44a068ec9cfdc1"
+ integrity sha512-8wigI1pDFJO1m1IQWPguOK+nOsaAVRWkVdu+2te/rDcIR9QNvMzzou0+Lhfp3zKSVT4E6mEoGB/TWXXF5Iq0sQ==
+ dependencies:
+ "@storybook/client-logger" "6.0.26"
+ "@storybook/csf" "0.0.1"
+ "@storybook/theming" "6.0.26"
+ "@types/overlayscrollbars" "^1.9.0"
+ "@types/react-color" "^3.0.1"
+ "@types/react-syntax-highlighter" "11.0.4"
+ core-js "^3.0.1"
+ fast-deep-equal "^3.1.1"
+ global "^4.3.2"
+ lodash "^4.17.15"
+ markdown-to-jsx "^6.11.4"
+ memoizerific "^1.11.3"
+ overlayscrollbars "^1.10.2"
+ polished "^3.4.4"
+ popper.js "^1.14.7"
+ react "^16.8.3"
+ react-color "^2.17.0"
+ react-dom "^16.8.3"
+ react-popper-tooltip "^2.11.0"
+ react-syntax-highlighter "^12.2.1"
+ react-textarea-autosize "^8.1.1"
+ ts-dedent "^1.1.1"
+
"@storybook/core-events@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.21.tgz#2ce51e6d7524e7543dbb29571beac1dbeb4e5f40"
@@ -4021,6 +4143,13 @@
dependencies:
core-js "^3.0.1"
+"@storybook/core-events@6.0.26":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.26.tgz#61181c9a8610d26cc85d47f133a563879044ca2d"
+ integrity sha512-nWjS/+kMiw31OPgeJQaiFsJk9ZJJo3/d4c+kc6GOl2iC1H3Q4/5cm3NvJBn/7bUtKHmSFwfbDouj+XjUk5rZbQ==
+ dependencies:
+ core-js "^3.0.1"
+
"@storybook/core@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/core/-/core-6.0.21.tgz#105c2b90ab27e7b478cb1b7d10e9fe5aba5e0708"
@@ -4180,6 +4309,18 @@
memoizerific "^1.11.3"
qs "^6.6.0"
+"@storybook/router@6.0.26":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/router/-/router-6.0.26.tgz#5b991001afa7d7eb5e40c53cd4c58266b6f9edfd"
+ integrity sha512-kQ1LF/2gX3IkjS1wX7CsoeBc9ptHQzOsyax16rUyJa769DT5vMNtFtQxjNXMqSiSapPg2yrXJFKQNaoWvKgQEQ==
+ dependencies:
+ "@reach/router" "^1.3.3"
+ "@types/reach__router" "^1.3.5"
+ core-js "^3.0.1"
+ global "^4.3.2"
+ memoizerific "^1.11.3"
+ qs "^6.6.0"
+
"@storybook/semver@^7.3.2":
version "7.3.2"
resolved "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0"
@@ -4222,6 +4363,24 @@
resolve-from "^5.0.0"
ts-dedent "^1.1.1"
+"@storybook/theming@6.0.26":
+ version "6.0.26"
+ resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.0.26.tgz#e5b545fb2653dfd1b043b567197d490b1c3c0da3"
+ integrity sha512-9yon2ofb9a+RT1pdvn8Njydy7XRw0qXcIsMqGsJRKoZecmRRozqB6DxH9Gbdf1vRSbM9gYUUDjbiMDFz7+4RiQ==
+ dependencies:
+ "@emotion/core" "^10.0.20"
+ "@emotion/is-prop-valid" "^0.8.6"
+ "@emotion/styled" "^10.0.17"
+ "@storybook/client-logger" "6.0.26"
+ core-js "^3.0.1"
+ deep-object-diff "^1.1.0"
+ emotion-theming "^10.0.19"
+ global "^4.3.2"
+ memoizerific "^1.11.3"
+ polished "^3.4.4"
+ resolve-from "^5.0.0"
+ ts-dedent "^1.1.1"
+
"@storybook/ui@6.0.21":
version "6.0.21"
resolved "https://registry.npmjs.org/@storybook/ui/-/ui-6.0.21.tgz#5dac2b68a30f5dba5457e0315f58977e07138968"
@@ -9780,11 +9939,6 @@ deepmerge@4.2.2, deepmerge@^4.2.2:
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
-default-branch@^1.0.8:
- version "1.0.8"
- resolved "https://registry.npmjs.org/default-branch/-/default-branch-1.0.8.tgz#0e2f36a90e3b0d9f73cdf8e02841364ed35b8b54"
- integrity sha512-pViUZEnaxd/Hbu880MEXF7XqV8RJ1ssDlkvzx+woQhcKW8px3BrVDvwBGn09zRiKZ+gOLipK7ft5x3no+3vb8A==
-
default-gateway@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
@@ -10661,9 +10815,9 @@ escodegen@^1.14.1, escodegen@^1.9.1:
source-map "~0.6.1"
eslint-config-prettier@^6.0.0:
- version "6.10.0"
- resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz#7b15e303bf9c956875c948f6b21500e48ded6a7f"
- integrity sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==
+ version "6.14.0"
+ resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.14.0.tgz#390e7863a8ae99970981933826476169285b3a27"
+ integrity sha512-DbVwh0qZhAC7CNDWcq8cBdK6FcVHiMTKmCypOPWeZkp9hJ8xYwTaWSa6bb6cjfi8KOeJy0e9a8Izxyx+O4+gCQ==
dependencies:
get-stdin "^6.0.0"