Merge branch 'master' into eide/shortcut-plugin

This commit is contained in:
Marcus Eide
2021-05-07 17:06:23 +02:00
76 changed files with 740 additions and 291 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-explore': patch
---
Add "Organization" tab with a diagram
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/cli': patch
'@backstage/create-app': patch
'@backstage/plugin-catalog-graphql': patch
---
chore: bump `ts-node` versions to 9.1.1
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/catalog-model': patch
---
Renamed parameters to input in template schema
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': patch
---
Show error on task page if task does not exist.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
update plugins created to use react-use 17.2.4
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/core': patch
'@backstage/plugin-api-docs': patch
---
Fix state persisted in the URL make search input in the table toolbar lose their
focus.
+44
View File
@@ -0,0 +1,44 @@
---
'@backstage/core': patch
'@backstage/core-api': patch
'@backstage/integration-react': patch
'@backstage/plugin-api-docs': patch
'@backstage/plugin-badges': patch
'@backstage/plugin-bitrise': patch
'@backstage/plugin-catalog': patch
'@backstage/plugin-catalog-import': patch
'@backstage/plugin-catalog-react': patch
'@backstage/plugin-circleci': patch
'@backstage/plugin-cloudbuild': patch
'@backstage/plugin-code-coverage': patch
'@backstage/plugin-config-schema': patch
'@backstage/plugin-cost-insights': patch
'@backstage/plugin-explore': patch
'@backstage/plugin-fossa': patch
'@backstage/plugin-gcp-projects': patch
'@backstage/plugin-github-actions': patch
'@backstage/plugin-github-deployments': patch
'@backstage/plugin-gitops-profiles': patch
'@backstage/plugin-graphiql': patch
'@backstage/plugin-jenkins': patch
'@backstage/plugin-kafka': patch
'@backstage/plugin-kubernetes': patch
'@backstage/plugin-lighthouse': patch
'@backstage/plugin-newrelic': patch
'@backstage/plugin-org': patch
'@backstage/plugin-pagerduty': patch
'@backstage/plugin-register-component': patch
'@backstage/plugin-rollbar': patch
'@backstage/plugin-scaffolder': patch
'@backstage/plugin-search': patch
'@backstage/plugin-sentry': patch
'@backstage/plugin-sonarqube': patch
'@backstage/plugin-splunk-on-call': patch
'@backstage/plugin-tech-radar': patch
'@backstage/plugin-techdocs': patch
'@backstage/plugin-todo': patch
'@backstage/plugin-user-settings': patch
'@backstage/plugin-welcome': patch
---
chore: bump `react-use` dependency in all packages
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Add possibility to configure TLS verification for `gke` type clusters
@@ -33,6 +33,7 @@ kubernetes:
- type: 'gke'
projectId: 'gke-clusters'
region: 'europe-west1'
skipTLSVerify: true
```
### `serviceLocatorMethod`
@@ -129,6 +130,11 @@ The Google Cloud project to look for Kubernetes clusters in.
The Google Cloud region to look for Kubernetes clusters in. Defaults to all
regions.
##### `skipTLSVerify`
This determines whether or not the Kubernetes client verifies the TLS
certificate presented by the API server. Defaults to `false`.
### `customResources` (optional)
Configures which [custom resources][3] to look for when returning an entity's
+1 -1
View File
@@ -52,7 +52,7 @@
"react-hot-loader": "^4.12.21",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"zen-observable": "^0.8.15"
},
"devDependencies": {
+1 -1
View File
@@ -514,7 +514,7 @@ export interface TemplateEntityV1beta2 extends Entity {
id?: string;
name?: string;
action: string;
parameters?: JsonObject;
input?: JsonObject;
}>;
output?: {
[name: string]: string;
@@ -51,7 +51,7 @@ describe('templateEntityV1beta2Validator', () => {
id: 'fetch',
name: 'Fetch',
action: 'fetch:plan',
parameters: {
input: {
url: './template',
},
},
@@ -38,7 +38,7 @@ export interface TemplateEntityV1beta2 extends Entity {
id?: string;
name?: string;
action: string;
parameters?: JsonObject;
input?: JsonObject;
}>;
output?: { [name: string]: string };
};
@@ -125,7 +125,7 @@
"type": "string",
"description": "The name of the action to execute."
},
"parameters": {
"input": {
"type": "object",
"description": "A templated object describing the inputs to the action."
}
+1 -1
View File
@@ -143,7 +143,7 @@
"del": "^5.1.0",
"mock-fs": "^4.13.0",
"nodemon": "^2.0.2",
"ts-node": "^8.6.2"
"ts-node": "^9.1.1"
},
"files": [
"asset-types",
@@ -31,7 +31,7 @@
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^{{version '@backstage/cli'}}",
+1 -1
View File
@@ -38,7 +38,7 @@
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"zen-observable": "^0.8.15"
},
"devDependencies": {
+1 -1
View File
@@ -65,7 +65,7 @@
"react-sparklines": "^1.7.0",
"react-syntax-highlighter": "^15.4.3",
"react-text-truncate": "^0.16.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"remark-gfm": "^1.0.0",
"zen-observable": "^0.8.15"
},
+79 -57
View File
@@ -20,6 +20,7 @@ import {
makeStyles,
Typography,
useTheme,
withStyles,
} from '@material-ui/core';
// Material-table is not using the standard icons available in in material-ui. https://github.com/mbrn/material-table/issues/51
import AddBox from '@material-ui/icons/AddBox';
@@ -49,6 +50,7 @@ import MTable, {
} from 'material-table';
import React, {
forwardRef,
MutableRefObject,
ReactNode,
useCallback,
useEffect,
@@ -100,19 +102,20 @@ function extractValueByField(data: any, field: string): any | undefined {
return value;
}
const useHeaderStyles = makeStyles<BackstageTheme>(theme => ({
const StyledMTableHeader = withStyles(theme => ({
header: {
padding: theme.spacing(1, 2, 1, 2.5),
borderTop: `1px solid ${theme.palette.grey.A100}`,
borderBottom: `1px solid ${theme.palette.grey.A100}`,
color: theme.palette.textSubtle,
// withStyles hasn't a generic overload for theme
color: (theme as BackstageTheme).palette.textSubtle,
fontWeight: theme.typography.fontWeightBold,
position: 'static',
wordBreak: 'normal',
},
}));
}))(MTableHeader);
const useToolbarStyles = makeStyles<BackstageTheme>(theme => ({
const StyledMTableToolbar = withStyles(theme => ({
root: {
padding: theme.spacing(3, 0, 2.5, 2.5),
},
@@ -124,7 +127,7 @@ const useToolbarStyles = makeStyles<BackstageTheme>(theme => ({
searchField: {
paddingRight: theme.spacing(2),
},
}));
}))(MTableToolbar);
const useFilterStyles = makeStyles<BackstageTheme>(() => ({
root: {
@@ -208,6 +211,59 @@ export interface TableProps<T extends object = {}>
onStateChange?: (state: TableState) => any;
}
export function TableToolbar(toolbarProps: {
toolbarRef: MutableRefObject<any>;
setSearch: (value: string) => void;
onSearchChanged: (value: string) => void;
toggleFilters: () => void;
hasFilters: boolean;
selectedFiltersLength: number;
}) {
const {
toolbarRef,
setSearch,
hasFilters,
selectedFiltersLength,
toggleFilters,
} = toolbarProps;
const filtersClasses = useFilterStyles();
const onSearchChanged = useCallback(
(searchText: string) => {
toolbarProps.onSearchChanged(searchText);
setSearch(searchText);
},
[toolbarProps, setSearch],
);
if (hasFilters) {
return (
<div className={filtersClasses.root}>
<div className={filtersClasses.root}>
<IconButton onClick={toggleFilters} aria-label="filter list">
<FilterList />
</IconButton>
<Typography className={filtersClasses.title}>
Filters ({selectedFiltersLength})
</Typography>
</div>
<StyledMTableToolbar
{...toolbarProps}
ref={toolbarRef}
onSearchChanged={onSearchChanged}
/>
</div>
);
}
return (
<StyledMTableToolbar
{...toolbarProps}
ref={toolbarRef}
onSearchChanged={onSearchChanged}
/>
);
}
export function Table<T extends object = {}>({
columns,
options,
@@ -219,10 +275,7 @@ export function Table<T extends object = {}>({
onStateChange,
...props
}: TableProps<T>) {
const headerClasses = useHeaderStyles();
const toolbarClasses = useToolbarStyles();
const tableClasses = useTableStyles();
const filtersClasses = useFilterStyles();
const { data, ...propsWithoutData } = props;
@@ -230,9 +283,12 @@ export function Table<T extends object = {}>({
const calculatedInitialState = { ...defaultInitialState, ...initialState };
const [filtersOpen, toggleFilters] = useState(
const [filtersOpen, setFiltersOpen] = useState(
calculatedInitialState.filtersOpen,
);
const toggleFilters = useCallback(() => setFiltersOpen(v => !v), [
setFiltersOpen,
]);
const [selectedFiltersLength, setSelectedFiltersLength] = useState(0);
const [tableData, setTableData] = useState(data as any[]);
const [selectedFilters, setSelectedFilters] = useState(
@@ -379,64 +435,32 @@ export function Table<T extends object = {}>({
}));
};
const hasFilters = !!filters?.length;
const Toolbar = useCallback(
toolbarProps => {
const onSearchChanged = (searchText: string) => {
toolbarProps.onSearchChanged(searchText);
setSearch(searchText);
};
if (filters?.length) {
return (
<div className={filtersClasses.root}>
<div className={filtersClasses.root}>
<IconButton
onClick={() => toggleFilters(el => !el)}
aria-label="filter list"
>
<FilterList />
</IconButton>
<Typography className={filtersClasses.title}>
Filters ({selectedFiltersLength})
</Typography>
</div>
<MTableToolbar
classes={toolbarClasses}
{...toolbarProps}
ref={toolbarRef}
onSearchChanged={onSearchChanged}
/>
</div>
);
}
return (
<MTableToolbar
classes={toolbarClasses}
<TableToolbar
toolbarRef={toolbarRef}
setSearch={setSearch}
hasFilters={hasFilters}
selectedFiltersLength={selectedFiltersLength}
toggleFilters={toggleFilters}
{...toolbarProps}
ref={toolbarRef}
onSearchChanged={onSearchChanged}
/>
);
},
[
filters?.length,
selectedFiltersLength,
toggleFilters,
toolbarClasses,
filtersClasses,
setSearch,
toolbarRef,
],
[toggleFilters, hasFilters, selectedFiltersLength, setSearch, toolbarRef],
);
const hasNoRows = typeof data !== 'function' && data.length === 0;
const columnCount = columns.length;
const Body = useCallback(
bodyProps => {
if (emptyContent && typeof data !== 'function' && data.length === 0) {
if (emptyContent && hasNoRows) {
return (
<tbody>
<tr>
<td colSpan={columns.length}>{emptyContent}</td>
<td colSpan={columnCount}>{emptyContent}</td>
</tr>
</tbody>
);
@@ -444,7 +468,7 @@ export function Table<T extends object = {}>({
return <MTableBody {...bodyProps} />;
},
[data, emptyContent, columns],
[hasNoRows, emptyContent, columnCount],
);
return (
@@ -458,9 +482,7 @@ export function Table<T extends object = {}>({
)}
<MTable<T>
components={{
Header: headerProps => (
<MTableHeader classes={headerClasses} {...headerProps} />
),
Header: StyledMTableHeader,
Toolbar,
Body,
}}
+12 -11
View File
@@ -17,7 +17,7 @@
import { isEqual } from 'lodash';
import qs from 'qs';
import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useSearchParams } from 'react-router-dom';
import { useDebounce } from 'react-use';
function stringify(queryParams: any): string {
@@ -58,36 +58,37 @@ type SetQueryParams<T> = (params: T) => void;
export function useQueryParamState<T>(
stateName: string,
debounceTime: number = 100,
/** @deprecated Don't configure a custom debouceTime */
debounceTime: number = 250,
): [T | undefined, SetQueryParams<T>] {
const navigate = useNavigate();
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const searchParamsString = searchParams.toString();
const [queryParamState, setQueryParamState] = useState<T>(
extractState(location.search, stateName),
extractState(searchParamsString, stateName),
);
useEffect(() => {
const newState = extractState(location.search, stateName);
const newState = extractState(searchParamsString, stateName);
setQueryParamState(oldState =>
isEqual(newState, oldState) ? oldState : newState,
);
}, [location, stateName]);
}, [searchParamsString, setQueryParamState, stateName]);
useDebounce(
() => {
const queryString = joinQueryString(
location.search,
searchParamsString,
stateName,
queryParamState,
);
if (location.search !== queryString) {
navigate({ ...location, search: `?${queryString}` }, { replace: true });
if (searchParamsString !== queryString) {
setSearchParams(queryString, { replace: true });
}
},
debounceTime,
[queryParamState],
[setSearchParams, queryParamState, searchParamsString, stateName],
);
return [queryParamState, setQueryParamState];
+1 -1
View File
@@ -41,7 +41,7 @@
"@types/inquirer": "^7.3.1",
"@types/react-dev-utils": "^9.0.4",
"@types/recursive-readdir": "^2.2.0",
"ts-node": "^8.6.2"
"ts-node": "^9.1.1"
},
"peerDependencies": {
"@backstage/backend-common": "*",
+1 -1
View File
@@ -30,7 +30,7 @@
"commander": "^6.1.0",
"fs-extra": "^9.0.0",
"github-slugger": "^1.3.0",
"ts-node": "^8.6.2",
"ts-node": "^9.1.1",
"typescript": "^4.0.3"
},
"devDependencies": {
+1 -1
View File
@@ -34,7 +34,7 @@
"handlebars": "^4.7.3",
"pgtools": "^0.3.0",
"tree-kill": "^1.2.2",
"ts-node": "^8.6.2",
"ts-node": "^9.1.1",
"zombie": "^6.1.4"
},
"nodemonConfig": {
+1 -1
View File
@@ -29,7 +29,7 @@
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.3",
+1 -1
View File
@@ -45,7 +45,7 @@
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"swagger-ui-react": "^3.37.2"
},
"devDependencies": {
@@ -156,7 +156,6 @@ export const ApiExplorerTable = ({
}: ExplorerTableProps) => {
const [queryParamState, setQueryParamState] = useQueryParamState<TableState>(
'apiTable',
500,
);
if (error) {
+1 -1
View File
@@ -31,7 +31,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -33,7 +33,7 @@
"qs": "^6.9.6",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"recharts": "^1.8.5"
},
"devDependencies": {
@@ -185,7 +185,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
),
-- All the nodes that can be reached upwards from the descendants
ancestors(root_id, via_entity_ref, to_entity_ref) AS (
SELECT NULL, entity_ref, entity_ref
SELECT CAST(NULL as INT), entity_ref, entity_ref
FROM descendants
UNION
SELECT
@@ -235,7 +235,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
.withRecursive('ancestors', function ancestors(outer) {
return outer
.select({
root_id: tx.raw('NULL', []),
root_id: tx.raw('CAST(NULL as INT)', []),
via_entity_ref: 'entity_ref',
to_entity_ref: 'entity_ref',
})
+1 -1
View File
@@ -52,7 +52,7 @@
"eslint-plugin-graphql": "^4.0.0",
"msw": "^0.21.2",
"supertest": "^6.1.3",
"ts-node": "^8.10.2"
"ts-node": "^9.1.1"
},
"files": [
"dist"
+1 -1
View File
@@ -49,7 +49,7 @@
"react-hook-form": "^6.15.4",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"yaml": "^1.10.0"
},
"devDependencies": {
+1 -1
View File
@@ -37,7 +37,7 @@
"react": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -49,7 +49,7 @@
"react-helmet": "6.1.0",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -47,7 +47,7 @@
"react-lazylog": "^4.5.2",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -44,7 +44,7 @@
"react-lazylog": "^4.5.3",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -35,7 +35,7 @@
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"recharts": "^1.8.5"
},
"devDependencies": {
+1 -1
View File
@@ -31,7 +31,7 @@
"zen-observable": "^0.8.15",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -49,7 +49,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"recharts": "^1.8.5",
"regression": "^2.0.1",
"yup": "^0.29.3"
+1 -1
View File
@@ -42,7 +42,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
@@ -16,6 +16,7 @@
import { TabbedLayout } from '@backstage/core';
import React from 'react';
import { DomainExplorerContent } from '../DomainExplorerContent';
import { GroupsExplorerContent } from '../GroupsExplorerContent';
import { ToolExplorerContent } from '../ToolExplorerContent';
export const ExploreTabs = () => (
@@ -23,6 +24,9 @@ export const ExploreTabs = () => (
<TabbedLayout.Route path="domains" title="Domains">
<DomainExplorerContent />
</TabbedLayout.Route>
<TabbedLayout.Route path="groups" title="Groups">
<GroupsExplorerContent />
</TabbedLayout.Route>
<TabbedLayout.Route path="tools" title="Tools">
<ToolExplorerContent />
</TabbedLayout.Route>
@@ -0,0 +1,69 @@
/*
* 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 { ApiProvider, ApiRegistry } from '@backstage/core';
import {
catalogApiRef,
CatalogApi,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { Entity } from '@backstage/catalog-model';
import { renderInTestApp } from '@backstage/test-utils';
import React from 'react';
import { GroupsDiagram } from './GroupsDiagram';
describe('<GroupsDiagram />', () => {
beforeAll(() => {
Object.defineProperty(window.SVGElement.prototype, 'getBBox', {
value: () => ({ width: 100, height: 100 }),
configurable: true,
});
});
it('shows groups', async () => {
const catalogApi: Partial<CatalogApi> = {
getEntities: () =>
Promise.resolve({
items: [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Group',
metadata: {
name: 'group-a',
namespace: 'my-namespace',
},
spec: {
type: 'organization',
},
},
] as Entity[],
}),
};
const { getByText } = await renderInTestApp(
<ApiProvider apis={ApiRegistry.from([[catalogApiRef, catalogApi]])}>
<GroupsDiagram />
</ApiProvider>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
},
},
);
expect(getByText('my-namespace/group-a')).toBeInTheDocument();
});
});
@@ -0,0 +1,190 @@
/*
* Copyright 2021 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 {
RELATION_CHILD_OF,
stringifyEntityRef,
parseEntityRef,
} from '@backstage/catalog-model';
import {
catalogApiRef,
entityRouteRef,
getEntityRelations,
formatEntityRefTitle,
} from '@backstage/plugin-catalog-react';
import {
DependencyGraph,
DependencyGraphTypes,
Progress,
useApi,
ResponseErrorPanel,
Link,
useRouteRef,
configApiRef,
} from '@backstage/core';
import { makeStyles, Typography } from '@material-ui/core';
import ZoomOutMap from '@material-ui/icons/ZoomOutMap';
import React from 'react';
import { useAsync } from 'react-use';
import { BackstageTheme } from '@backstage/theme';
const useStyles = makeStyles((theme: BackstageTheme) => ({
organizationNode: {
fill: 'coral',
stroke: theme.palette.border,
},
groupNode: {
fill: 'yellowgreen',
stroke: theme.palette.border,
},
}));
function RenderNode(props: DependencyGraphTypes.RenderNodeProps<any>) {
const classes = useStyles();
const catalogEntityRoute = useRouteRef(entityRouteRef);
if (props.node.id === 'root') {
return (
<g>
<rect
width={180}
height={80}
rx={20}
className={classes.organizationNode}
/>
<text
x={90}
y={45}
textAnchor="middle"
alignmentBaseline="baseline"
style={{ fontWeight: 'bold' }}
>
{props.node.name}
</text>
</g>
);
}
const ref = parseEntityRef(props.node.id);
return (
<g>
<rect width={180} height={80} rx={20} className={classes.groupNode} />
<Link
to={catalogEntityRoute({
kind: ref.kind,
namespace: ref.namespace,
name: ref.name,
})}
>
<text
x={90}
y={45}
textAnchor="middle"
alignmentBaseline="baseline"
style={{ fontWeight: 'bold' }}
>
{props.node.name}
</text>
</Link>
</g>
);
}
/**
* Dynamically generates a diagram of groups registered in the catalog.
*/
export function GroupsDiagram() {
const nodes = new Array<{ id: string; kind: string; name: string }>();
const edges = new Array<{ from: string; to: string; label: string }>();
const configApi = useApi(configApiRef);
const catalogApi = useApi(catalogApiRef);
const organizationName =
configApi.getOptionalString('organization.name') ?? 'Backstage';
const { loading, error, value: catalogResponse } = useAsync(() => {
return catalogApi.getEntities({
filter: {
kind: ['Group'],
},
});
}, [catalogApi]);
if (loading) {
return <Progress />;
} else if (error) {
return <ResponseErrorPanel error={error} />;
}
// the root of this diagram is the organization
nodes.push({
id: 'root',
kind: 'Organization',
name: organizationName,
});
for (const catalogItem of catalogResponse?.items || []) {
const currentItemId = stringifyEntityRef(catalogItem);
nodes.push({
id: stringifyEntityRef(catalogItem),
kind: catalogItem.kind,
name: formatEntityRefTitle(catalogItem, { defaultKind: 'Group' }),
});
// Edge to parent
const catalogItemRelations_childOf = getEntityRelations(
catalogItem,
RELATION_CHILD_OF,
);
// if no parent is found, link the node to the root
if (catalogItemRelations_childOf.length === 0) {
edges.push({
from: currentItemId,
to: 'root',
label: '',
});
}
catalogItemRelations_childOf.forEach(relation => {
edges.push({
from: currentItemId,
to: stringifyEntityRef(relation),
label: '',
});
});
}
return (
<>
<DependencyGraph
nodes={nodes}
edges={edges}
nodeMargin={10}
direction={DependencyGraphTypes.Direction.RIGHT_LEFT}
renderNode={RenderNode}
/>
<Typography
variant="caption"
style={{ display: 'block', textAlign: 'right' }}
>
<ZoomOutMap style={{ verticalAlign: 'bottom' }} /> Use pinch &amp; zoom
to move around the diagram.
</Typography>
</>
);
}
@@ -0,0 +1,103 @@
/*
* 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 { Entity } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry } from '@backstage/core';
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
import { renderInTestApp } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { GroupsExplorerContent } from '../GroupsExplorerContent';
describe('<GroupsExplorerContent />', () => {
const catalogApi: jest.Mocked<typeof catalogApiRef.T> = {
addLocation: jest.fn(_a => new Promise(() => {})),
getEntities: jest.fn(),
getOriginLocationByEntity: jest.fn(),
getLocationByEntity: jest.fn(),
getLocationById: jest.fn(),
removeLocationById: jest.fn(),
removeEntityByUid: jest.fn(),
getEntityByName: jest.fn(),
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider apis={ApiRegistry.with(catalogApiRef, catalogApi)}>
{children}
</ApiProvider>
);
beforeEach(() => {
jest.resetAllMocks();
Object.defineProperty(window.SVGElement.prototype, 'getBBox', {
value: () => ({ width: 100, height: 100 }),
configurable: true,
});
});
it('renders a groups diagram', async () => {
const entities: Entity[] = [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Group',
metadata: {
name: 'group-a',
namespace: 'my-namespace',
},
spec: {
type: 'organization',
},
},
];
catalogApi.getEntities.mockResolvedValue({ items: entities });
const { getByText } = await renderInTestApp(
<Wrapper>
<GroupsExplorerContent />
</Wrapper>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
},
},
);
await waitFor(() => {
expect(getByText('my-namespace/group-a')).toBeInTheDocument();
});
});
it('renders a friendly error if it cannot collect domains', async () => {
const catalogError = new Error('Network timeout');
catalogApi.getEntities.mockRejectedValueOnce(catalogError);
const { getByText } = await renderInTestApp(
<Wrapper>
<GroupsExplorerContent />
</Wrapper>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
},
},
);
await waitFor(() =>
expect(getByText(/Warning: Network timeout/)).toBeInTheDocument(),
);
});
});
@@ -0,0 +1,30 @@
/*
* Copyright 2021 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 { Content, ContentHeader, SupportButton } from '@backstage/core';
import React from 'react';
import { GroupsDiagram } from './GroupsDiagram';
export const GroupsExplorerContent = () => {
return (
<Content noPadding>
<ContentHeader title="Groups">
<SupportButton>Explore your groups.</SupportButton>
</ContentHeader>
<GroupsDiagram />
</Content>
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2021 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.
*/
export { GroupsExplorerContent } from './GroupsExplorerContent';
+1 -1
View File
@@ -44,7 +44,7 @@
"p-limit": "^3.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -38,7 +38,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -47,7 +47,7 @@
"react-lazylog": "^4.5.3",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -31,7 +31,7 @@
"luxon": "^1.26.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -39,7 +39,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -40,7 +40,7 @@
"graphql": "15.5.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -44,7 +44,7 @@
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -30,7 +30,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+33 -4
View File
@@ -14,14 +14,43 @@
* limitations under the License.
*/
import { ClusterLocatorMethod, CustomResource } from './src/types';
export interface Config {
kubernetes?: {
serviceLocatorMethod: {
type: 'multiTenant';
};
clusterLocatorMethods: ClusterLocatorMethod[];
customResources?: CustomResource[];
clusterLocatorMethods: Array<
| {
/** @visibility frontend */
type: 'gke';
/** @visibility frontend */
projectId: string;
/** @visibility frontend */
region?: string;
/** @visibility frontend */
skipTLSVerify?: boolean;
}
| {
/** @visibility frontend */
type: 'config';
clusters: Array<{
/** @visibility frontend */
url: string;
/** @visibility frontend */
name: string;
/** @visibility secret */
serviceAccountToken?: string;
/** @visibility frontend */
authProvider: 'aws' | 'google' | 'serviceAccount';
/** @visibility frontend */
skipTLSVerify?: boolean;
}>;
}
>;
customResources?: Array<{
group: string;
apiVersion: string;
plural: string;
}>;
};
}
@@ -106,6 +106,7 @@ describe('GkeClusterLocator', () => {
authProvider: 'google',
name: 'some-cluster',
url: 'https://1.2.3.4',
skipTLSVerify: false,
},
]);
expect(mockedListClusters).toBeCalledTimes(1);
@@ -141,6 +142,7 @@ describe('GkeClusterLocator', () => {
authProvider: 'google',
name: 'some-cluster',
url: 'https://1.2.3.4',
skipTLSVerify: false,
},
]);
expect(mockedListClusters).toBeCalledTimes(1);
@@ -181,11 +183,13 @@ describe('GkeClusterLocator', () => {
authProvider: 'google',
name: 'some-cluster',
url: 'https://1.2.3.4',
skipTLSVerify: false,
},
{
authProvider: 'google',
name: 'some-other-cluster',
url: 'https://6.7.8.9',
skipTLSVerify: false,
},
]);
expect(mockedListClusters).toBeCalledTimes(1);
@@ -18,28 +18,28 @@ import { Config } from '@backstage/config';
import * as container from '@google-cloud/container';
import { ClusterDetails, KubernetesClustersSupplier } from '../types/types';
export class GkeClusterLocator implements KubernetesClustersSupplier {
private readonly projectId: string;
private readonly region: string | undefined;
private readonly client: container.v1.ClusterManagerClient;
type GkeClusterLocatorOptions = {
projectId: string;
region?: string;
skipTLSVerify?: boolean;
};
export class GkeClusterLocator implements KubernetesClustersSupplier {
constructor(
projectId: string,
client: container.v1.ClusterManagerClient,
region?: string,
) {
this.projectId = projectId;
this.region = region;
this.client = client;
}
private readonly options: GkeClusterLocatorOptions,
private readonly client: container.v1.ClusterManagerClient,
) {}
static fromConfigWithClient(
config: Config,
client: container.v1.ClusterManagerClient,
): GkeClusterLocator {
const projectId = config.getString('projectId');
const region = config.getOptionalString('region');
return new GkeClusterLocator(projectId, client, region);
const options = {
projectId: config.getString('projectId'),
region: config.getOptionalString('region') ?? '-',
skipTLSVerify: config.getOptionalBoolean('skipTLSVerify') ?? false,
};
return new GkeClusterLocator(options, client);
}
static fromConfig(config: Config): GkeClusterLocator {
@@ -50,9 +50,9 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
}
async getClusters(): Promise<ClusterDetails[]> {
const region = this.region ?? '-';
const { projectId, region, skipTLSVerify } = this.options;
const request = {
parent: `projects/${this.projectId}/locations/${region}`,
parent: `projects/${projectId}/locations/${region}`,
};
try {
@@ -62,10 +62,11 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
name: r.name ?? 'unknown',
url: `https://${r.endpoint ?? ''}`,
authProvider: 'google',
skipTLSVerify,
}));
} catch (e) {
throw new Error(
`There was an error retrieving clusters from GKE for projectId=${this.projectId} region=${region} : [${e.message}]`,
`There was an error retrieving clusters from GKE for projectId=${projectId} region=${region} : [${e.message}]`,
);
}
}
@@ -19,50 +19,6 @@ import type {
KubernetesFetchError,
} from '@backstage/plugin-kubernetes-common';
export type ClusterLocatorMethod =
| ConfigClusterLocatorMethod
| GKEClusterLocatorMethod;
export interface ConfigClusterLocatorMethod {
/**
* @visibility frontend
*/
type: 'config';
clusters: {
/**
* @visibility frontend
*/
url: string;
/**
* @visibility frontend
*/
name: string;
/**
* @visibility secret
*/
serviceAccountToken: string | undefined;
/**
* @visibility frontend
*/
authProvider: 'aws' | 'google' | 'serviceAccount';
}[];
}
export interface GKEClusterLocatorMethod {
/**
* @visibility frontend
*/
type: 'gke';
/**
* @visibility frontend
*/
projectId: string;
/**
* @visibility frontend
*/
region?: string;
}
export interface CustomResource {
group: string;
apiVersion: string;
+1 -1
View File
@@ -46,7 +46,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -43,7 +43,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -38,7 +38,7 @@
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -32,7 +32,7 @@
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -43,7 +43,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -42,7 +42,7 @@
"react-hook-form": "^6.15.4",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -44,7 +44,7 @@
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-sparklines": "^1.7.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+2 -1
View File
@@ -33,6 +33,7 @@
"@backstage/catalog-client": "^0.3.11",
"@backstage/catalog-model": "^0.7.8",
"@backstage/config": "^0.1.5",
"@backstage/errors": "^0.1.1",
"@backstage/core": "^0.7.8",
"@backstage/integration": "^0.5.2",
"@backstage/integration-react": "^0.1.1",
@@ -54,7 +55,7 @@
"react-lazylog": "^4.5.2",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"use-immer": "^0.5.1",
"zen-observable": "^0.8.15"
},
+15 -5
View File
@@ -22,6 +22,7 @@ import {
IdentityApi,
Observable,
} from '@backstage/core';
import { ResponseError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import ObservableImpl from 'zen-observable';
import { ListActionsResponse, ScaffolderTask, Status } from './types';
@@ -128,9 +129,7 @@ export class ScaffolderClient implements ScaffolderApi {
});
if (!response.ok) {
throw new Error(
`Failed to fetch template parameter schema, ${await response.text()}`,
);
throw ResponseError.fromResponse(response);
}
const schema: TemplateParameterSchema = await response.json();
@@ -173,9 +172,15 @@ export class ScaffolderClient implements ScaffolderApi {
const token = await this.identityApi.getIdToken();
const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder');
const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`;
return fetch(url, {
const response = await fetch(url, {
headers: token ? { Authorization: `Bearer ${token}` } : {},
}).then(x => x.json());
});
if (!response.ok) {
throw ResponseError.fromResponse(response);
}
return await response.json();
}
streamLogs({
@@ -234,6 +239,11 @@ export class ScaffolderClient implements ScaffolderApi {
async listActions(): Promise<ListActionsResponse> {
const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder');
const response = await fetch(`${baseUrl}/v2/actions`);
if (!response.ok) {
throw ResponseError.fromResponse(response);
}
return await response.json();
}
}
+1 -1
View File
@@ -41,7 +41,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -42,7 +42,7 @@
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-sparklines": "^1.7.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"timeago.js": "^4.0.2"
},
"devDependencies": {
+1 -1
View File
@@ -44,7 +44,7 @@
"rc-progress": "^3.0.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -42,7 +42,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -40,7 +40,7 @@
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -47,7 +47,7 @@
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"react-use": "^17.2.4",
"sanitize-html": "^2.3.2"
},
"devDependencies": {
+1 -1
View File
@@ -36,7 +36,7 @@
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -38,7 +38,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+1 -1
View File
@@ -38,7 +38,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.6.10",
+23 -98
View File
@@ -2351,18 +2351,18 @@
tslib "~2.2.0"
"@graphql-codegen/typescript-resolvers@^1.17.7":
version "1.18.2"
resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-1.18.2.tgz#7513b92df7c5a0d3c27342c591ada7340696cf8f"
integrity sha512-aWfRR5y1gXCPUNK7zaUiSbmceqidvZ38mNHIBvXmZArRigyz1QZgN26kKpXjJjtFbPvROPnlGsYcZBReybvZXA==
version "1.19.1"
resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-1.19.1.tgz#56677ec56c1ca7174d22a2f236e3fb7f6503e708"
integrity sha512-KdCVfg2u2RMbHu7eV9SOh5rmfnEQaMsQ0k8741bMbBmCESLnrWltujF2RT1OPN7WCn7xJejBtrFg/3UgT0fNug==
dependencies:
"@graphql-codegen/plugin-helpers" "^1.18.2"
"@graphql-codegen/typescript" "^1.21.0"
"@graphql-codegen/visitor-plugin-common" "^1.18.3"
"@graphql-codegen/plugin-helpers" "^1.18.5"
"@graphql-codegen/typescript" "^1.22.0"
"@graphql-codegen/visitor-plugin-common" "^1.20.0"
"@graphql-tools/utils" "^7.0.0"
auto-bind "~4.0.0"
tslib "~2.1.0"
tslib "~2.2.0"
"@graphql-codegen/typescript@^1.17.7", "@graphql-codegen/typescript@^1.21.0":
"@graphql-codegen/typescript@^1.17.7", "@graphql-codegen/typescript@^1.22.0":
version "1.22.0"
resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.22.0.tgz#d05be3a971e5d75a076a43e123b6330f4366a6ab"
integrity sha512-YzN/3MBYHrP110m8JgUWQIHt7Ivi3JXiq0RT5XNx/F9mVOSbZz6Ezbaji8YJA3y04Gl2f6ZgtdGazWANUvcOcg==
@@ -2372,7 +2372,7 @@
auto-bind "~4.0.0"
tslib "~2.2.0"
"@graphql-codegen/visitor-plugin-common@^1.18.3", "@graphql-codegen/visitor-plugin-common@^1.20.0":
"@graphql-codegen/visitor-plugin-common@^1.20.0":
version "1.20.0"
resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.20.0.tgz#38d829eab7370c79aa5229190788f94adcae8f76"
integrity sha512-AYrpy8NA3DpvhDLqYGerQRv44S+YAMPKtwT8x9GNVjzP0gVfmqi3gG1bDWbP5sm6kOZKvDC0kTxGePuBSZerxw==
@@ -2664,16 +2664,7 @@
camel-case "4.1.1"
tslib "~2.0.1"
"@graphql-tools/utils@^7.0.0", "@graphql-tools/utils@^7.1.0":
version "7.2.6"
resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.2.6.tgz#5d974cbdec5ddf4d7fdc593816335512ee5fe4de"
integrity sha512-/kY7Nb+cCHi/MvU3tjz3KrXzuJNWMlnn7EoWazLmpDvl6b2Qt69hlVoPd5zQtKlGib35zZw9NZ5zs5qTAw8Y9g==
dependencies:
"@ardatan/aggregate-error" "0.0.6"
camel-case "4.1.2"
tslib "~2.1.0"
"@graphql-tools/utils@^7.1.2", "@graphql-tools/utils@^7.7.0":
"@graphql-tools/utils@^7.0.0", "@graphql-tools/utils@^7.1.0", "@graphql-tools/utils@^7.1.2", "@graphql-tools/utils@^7.7.0":
version "7.7.1"
resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.7.1.tgz#81f32cb4819b34b3a378d51ab2cd60935977f0b4"
integrity sha512-SFT4/dTfrwWer1wSOLU+jqgv3oa/xTR8q+MiNbE9nCH2FXyMsqIOaXKm9wHfKIWFWHozqBdcnwFkQZrdD7H2TQ==
@@ -8883,11 +8874,6 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
bowser@^1.7.3:
version "1.9.4"
resolved "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a"
integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==
boxen@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
@@ -10820,14 +10806,6 @@ css-tree@1.0.0-alpha.37:
mdn-data "2.0.4"
source-map "^0.6.1"
css-tree@^1.0.0-alpha.28:
version "1.0.0-alpha.39"
resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb"
integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==
dependencies:
mdn-data "2.0.6"
source-map "^0.6.1"
css-tree@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz#9ae393b5dafd7dae8a622475caec78d3d8fbd7b5"
@@ -10982,7 +10960,7 @@ cssstyle@^2.2.0:
dependencies:
cssom "~0.3.6"
csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.5, csstype@^2.5.7, csstype@^2.6.7:
csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.7:
version "2.6.9"
resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
@@ -13204,11 +13182,6 @@ fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3:
resolved "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53"
integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==
fastest-stable-stringify@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028"
integrity sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg=
fastest-stable-stringify@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76"
@@ -15436,14 +15409,6 @@ init-package-json@^2.0.2:
validate-npm-package-license "^3.0.4"
validate-npm-package-name "^3.0.0"
inline-style-prefixer@^4.0.0:
version "4.0.2"
resolved "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz#d390957d26f281255fe101da863158ac6eb60911"
integrity sha512-N8nVhwfYga9MiV9jWlwfdj1UDIaZlBFu4cJSJkIr7tZX7sHpHhGR5su1qdpW+7KPL8ISTvCIkcaFi/JdBknvPg==
dependencies:
bowser "^1.7.3"
css-in-js-utils "^2.0.0"
inline-style-prefixer@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.0.tgz#f73d5dbf2855733d6b153a4d24b7b47a73e9770b"
@@ -18361,11 +18326,6 @@ mdn-data@2.0.4:
resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
mdn-data@2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978"
integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
@@ -19074,21 +19034,7 @@ nan@2.14.1, nan@^2.12.1, nan@^2.14.0:
resolved "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
nano-css@^5.1.0, nano-css@^5.2.1:
version "5.3.0"
resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.3.0.tgz#9d3cd29788d48b6a07f52aa4aec7cf4da427b6b5"
integrity sha512-uM/9NGK9/E9/sTpbIZ/bQ9xOLOIHZwrrb/CRlbDHBU/GFS7Gshl24v/WJhwsVViWkpOXUmiZ66XO7fSB4Wd92Q==
dependencies:
css-tree "^1.0.0-alpha.28"
csstype "^2.5.5"
fastest-stable-stringify "^1.0.1"
inline-style-prefixer "^4.0.0"
rtl-css-js "^1.9.0"
sourcemap-codec "^1.4.1"
stacktrace-js "^2.0.0"
stylis "3.5.0"
nano-css@^5.3.1:
nano-css@^5.1.0, nano-css@^5.2.1, nano-css@^5.3.1:
version "5.3.1"
resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.3.1.tgz#b709383e07ad3be61f64edffacb9d98250b87a1f"
integrity sha512-ENPIyNzANQRyYVvb62ajDd7PAyIgS2LIUnT9ewih4yrXSZX4hKoUwssy8WjUH++kEOA5wUTMgNnV7ko5n34kUA==
@@ -21586,9 +21532,9 @@ prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0,
react-is "^16.8.1"
property-expr@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330"
integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g==
version "2.0.4"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910"
integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==
property-information@^5.0.0:
version "5.4.0"
@@ -22441,10 +22387,10 @@ react-use@^15.3.3, react-use@^15.3.6:
ts-easing "^0.2.0"
tslib "^2.0.0"
react-use@^17.2.1:
version "17.2.1"
resolved "https://registry.npmjs.org/react-use/-/react-use-17.2.1.tgz#c81e12544115ed049c7deba1e3bb3d977dfee9b8"
integrity sha512-9r51/at7/Nr/nEP4CsHz+pl800EAqhIY9R6O68m68kaWc8slDAfx1UrIedQqpsb4ImddFYb+6hF1i5Vj4u4Cnw==
react-use@^17.2.1, react-use@^17.2.4:
version "17.2.4"
resolved "https://registry.npmjs.org/react-use/-/react-use-17.2.4.tgz#1f89be3db0a8237c79253db0a15e12bbe3cfeff1"
integrity sha512-vQGpsAM0F5UIlshw5UI8ULGPS4yn5rm7/qvn3T1Gnkrz7YRMEEMh+ynKcmRloOyiIeLvKWiQjMiwRGtdbgs5qQ==
dependencies:
"@types/js-cookie" "^2.2.6"
"@xobotyi/scrollbar-width" "^1.9.5"
@@ -23352,7 +23298,7 @@ rsvp@^4.8.4:
resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
rtl-css-js@^1.14.0, rtl-css-js@^1.9.0:
rtl-css-js@^1.14.0:
version "1.14.0"
resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.14.0.tgz#daa4f192a92509e292a0519f4b255e6e3c076b7d"
integrity sha512-Dl5xDTeN3e7scU1cWX8c9b6/Nqz3u/HgR4gePc1kWXYiQWVQbKCEyK6+Hxve9LbcJ5EieHy1J9nJCN3grTtGwg==
@@ -23509,12 +23455,7 @@ schema-utils@^3.0.0:
ajv "^6.12.5"
ajv-keywords "^3.5.2"
screenfull@^5.0.0:
version "5.0.2"
resolved "https://registry.npmjs.org/screenfull/-/screenfull-5.0.2.tgz#b9acdcf1ec676a948674df5cd0ff66b902b0bed7"
integrity sha512-cCF2b+L/mnEiORLN5xSAz6H3t18i2oHh9BA8+CQlAh5DRw2+NFAGQJOSYbcGw8B2k04g/lVvFcfZ83b3ysH5UQ==
screenfull@^5.1.0:
screenfull@^5.0.0, screenfull@^5.1.0:
version "5.1.0"
resolved "https://registry.npmjs.org/screenfull/-/screenfull-5.1.0.tgz#85c13c70f4ead4c1b8a935c70010dfdcd2c0e5c8"
integrity sha512-dYaNuOdzr+kc6J6CFcBrzkLCfyGcMg+gWkJ8us93IQ7y1cevhQAugFsaCdMHb6lw8KV3xPzSxzH7zM1dQap9mA==
@@ -24084,7 +24025,7 @@ source-map@^0.7.3:
resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
sourcemap-codec@^1.4.1, sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
@@ -24317,7 +24258,7 @@ stacktrace-gps@^3.0.4:
source-map "0.5.6"
stackframe "^1.1.1"
stacktrace-js@^2.0.0, stacktrace-js@^2.0.2:
stacktrace-js@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b"
integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==
@@ -24737,11 +24678,6 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
stylis@3.5.0:
version "3.5.0"
resolved "https://registry.npmjs.org/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1"
integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw==
stylis@^4.0.6:
version "4.0.7"
resolved "https://registry.npmjs.org/stylis/-/stylis-4.0.7.tgz#412a90c28079417f3d27c028035095e4232d2904"
@@ -25588,17 +25524,6 @@ ts-log@^2.2.3:
resolved "https://registry.npmjs.org/ts-log/-/ts-log-2.2.3.tgz#4da5640fe25a9fb52642cd32391c886721318efb"
integrity sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w==
ts-node@^8.10.2, ts-node@^8.6.2:
version "8.10.2"
resolved "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==
dependencies:
arg "^4.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.17"
yn "3.1.1"
ts-node@^9, ts-node@^9.1.1:
version "9.1.1"
resolved "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"