Merge branch 'master' of https://github.com/backstage/backstage into marley/7641-consume-exported-ado-types

This commit is contained in:
Marley Powell
2021-11-09 07:59:38 +00:00
37 changed files with 1063 additions and 178 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search-backend-module-elasticsearch': patch
---
Pinning version of elastic search client to 7.13.0 to prevent breaking change towards third party ElasticSearch clusters on 7.14.0.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-code-coverage': patch
---
Change represented test date from epoch to something more human friendly. Round test coverage to 2 decimal places.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/config-loader': patch
---
Bump msw to the same version as the rest
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
fixed route resolving (issue #7741) when user cannot select a tab in any of the tabbed pages (like the Catalog page) if it shares the same initial letters as a preceding tab. (i.e. where tab with a path of /ci is followed by a path of /ci-2, user cannot select /ci-2 as /ci will always be selected first).
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Allow singleton and flexibly nested EntityFilters
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---
export `loadIdentityOwnerRefs` and `loadCatalogOwnerRefs` all the way
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Add Theme Overrides for Sidebar
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-azure-devops': patch
---
Simplified queue time calculation in `BuildTable`.
@@ -0,0 +1,23 @@
name: 'Snyk Github Issue Sync'
on:
schedule:
- cron: '0 */4 * * *'
jobs:
sync:
steps:
- uses: actions/checkout@v2
- name: Create Snyk report
uses: snyk/actions/node@master
with:
args: >
--yarn-workspaces
--org=backstage-dgh
--strict-out-of-sync=false
--json-file-output=snyk.json
json: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Update Github issues
run: yarn ts-node scripts/snyk-github-issue-sync.ts
+1
View File
@@ -63,3 +63,4 @@
| [Splunk](https://www.splunk.com) | [@tonytamsf](https://github.com/tonytamsf) | Developer portal as a centralized place to find people, services, documentation, escalation policies and give bravos. This portal is also being used as a centralized search engine for engineering specific documentation. |
| [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc. |
| [Volvofinans Bank](https://www.volvofinans.se) | [Johan Hammar](https://github.com/johanhammar) | Developer portal enabling engineers to manage and explore software and documentation. |
| [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling |
+2 -1
View File
@@ -275,7 +275,8 @@
"architecture-decisions/adrs-adr008",
"architecture-decisions/adrs-adr009",
"architecture-decisions/adrs-adr010",
"architecture-decisions/adrs-adr011"
"architecture-decisions/adrs-adr011",
"architecture-decisions/adrs-adr012"
],
"FAQ": ["FAQ"]
}
+3 -1
View File
@@ -58,9 +58,10 @@
"@microsoft/tsdoc": "^0.13.2"
},
"devDependencies": {
"@types/webpack": "^5.28.0",
"@changesets/cli": "^2.14.0",
"@octokit/rest": "^18.12.0",
"@spotify/prettier-config": "^11.0.0",
"@types/webpack": "^5.28.0",
"command-exists": "^1.2.9",
"concurrently": "^6.0.0",
"eslint-plugin-notice": "^0.9.10",
@@ -68,6 +69,7 @@
"husky": "^6.0.0",
"lerna": "^4.0.0",
"lint-staged": "^11.1.2",
"minimist": "^1.2.5",
"prettier": "^2.2.1",
"shx": "^0.3.2",
"yarn-lock-check": "^1.0.5"
+1 -1
View File
@@ -53,7 +53,7 @@
"@types/node": "^14.14.32",
"@types/yup": "^0.29.13",
"mock-fs": "^5.1.0",
"msw": "^0.29.0"
"msw": "^0.35.0"
},
"files": [
"dist"
@@ -31,6 +31,12 @@ const testRoute2 = {
children: <div>tabbed-test-content-2</div>,
};
const testRoute3 = {
title: 'tabbed-test-title-3',
path: '/some-other-path-similar',
children: <div>tabbed-test-content-3</div>,
};
describe('RoutedTabs', () => {
it('renders simplest case', async () => {
const rendered = await renderInTestApp(
@@ -46,7 +52,7 @@ describe('RoutedTabs', () => {
<Routes>
<Route
path="/*"
element={<RoutedTabs routes={[testRoute1, testRoute2]} />}
element={<RoutedTabs routes={[testRoute1, testRoute2, testRoute3]} />}
/>
</Routes>,
);
@@ -61,6 +67,13 @@ describe('RoutedTabs', () => {
expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
const thirdTab = rendered.queryAllByRole('tab')[2];
act(() => {
fireEvent.click(thirdTab);
});
expect(rendered.getByText('tabbed-test-title-3')).toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-content-3')).toBeInTheDocument();
});
describe('correctly delegates nested links', () => {
@@ -33,9 +33,15 @@ export function useSelectedSubRoute(subRoutes: SubRoute[]): {
element: children,
}));
const element = useRoutes(routes) ?? subRoutes[0].children;
// TODO: remove once react-router updated
const sortedRoutes = routes.sort((a, b) =>
// remove "/*" symbols from path end before comparing
b.path.replace(/\/\*$/, '').localeCompare(a.path.replace(/\/\*$/, '')),
);
const [matchedRoute] = matchRoutes(routes, `/${params['*']}`) ?? [];
const element = useRoutes(sortedRoutes) ?? subRoutes[0].children;
const [matchedRoute] = matchRoutes(sortedRoutes, `/${params['*']}`) ?? [];
const foundIndex = matchedRoute
? subRoutes.findIndex(t => `${t.path}/*` === matchedRoute.route.path)
: 0;
@@ -350,21 +350,30 @@ export function SidebarSearchField(props: SidebarSearchFieldProps) {
);
}
export const SidebarSpace = styled('div')({
flex: 1,
});
export const SidebarSpace = styled('div')(
{
flex: 1,
},
{ name: 'BackstageSidebarSpace' },
);
export const SidebarSpacer = styled('div')({
height: 8,
});
export const SidebarSpacer = styled('div')(
{
height: 8,
},
{ name: 'BackstageSidebarSpacer' },
);
export const SidebarDivider = styled('hr')({
height: 1,
width: '100%',
background: '#383838',
border: 'none',
margin: '12px 0px',
});
export const SidebarDivider = styled('hr')(
{
height: 1,
width: '100%',
background: '#383838',
border: 'none',
margin: '12px 0px',
},
{ name: 'BackstageSidebarDivider' },
);
const styledScrollbar = (theme: Theme): CreateCSSProperties => ({
overflowY: 'auto',
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+47
View File
@@ -0,0 +1,47 @@
## API Report File for "@backstage/plugin-azure-devops-common"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
// Warning: (ae-missing-release-tag) "BuildResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export enum BuildResult {
Canceled = 32,
Failed = 8,
None = 0,
PartiallySucceeded = 4,
Succeeded = 2,
}
// Warning: (ae-missing-release-tag) "BuildStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export enum BuildStatus {
All = 47,
Cancelling = 4,
Completed = 2,
InProgress = 1,
None = 0,
NotStarted = 32,
Postponed = 8,
}
// Warning: (ae-missing-release-tag) "RepoBuild" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type RepoBuild = {
id?: number;
title: string;
link?: string;
status?: BuildStatus;
result?: BuildResult;
queueTime?: Date;
startTime?: Date;
finishTime?: Date;
source: string;
uniqueName?: string;
};
// (No @packageDocumentation comment for this package)
```
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@backstage/plugin-azure-devops-common",
"version": "0.0.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/azure-devops-common"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli build",
"lint": "backstage-cli lint",
"test": "backstage-cli test --passWithNoTests",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.8.1"
},
"files": [
"dist"
]
}
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './types';
+82
View File
@@ -0,0 +1,82 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export enum BuildResult {
/**
* No result
*/
None = 0,
/**
* The build completed successfully.
*/
Succeeded = 2,
/**
* The build completed compilation successfully but had other errors.
*/
PartiallySucceeded = 4,
/**
* The build completed unsuccessfully.
*/
Failed = 8,
/**
* The build was canceled before starting.
*/
Canceled = 32,
}
export enum BuildStatus {
/**
* No status.
*/
None = 0,
/**
* The build is currently in progress.
*/
InProgress = 1,
/**
* The build has completed.
*/
Completed = 2,
/**
* The build is cancelling
*/
Cancelling = 4,
/**
* The build is inactive in the queue.
*/
Postponed = 8,
/**
* The build has not yet started.
*/
NotStarted = 32,
/**
* All status.
*/
All = 47,
}
export type RepoBuild = {
id?: number;
title: string;
link?: string;
status?: BuildStatus;
result?: BuildResult;
queueTime?: Date;
startTime?: Date;
finishTime?: Date;
source: string;
uniqueName?: string;
};
@@ -0,0 +1,94 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
BuildResult,
BuildStatus,
RepoBuild,
} from '@backstage/plugin-azure-devops-common';
import { BuildTable } from './BuildTable';
import { MemoryRouter } from 'react-router';
import React from 'react';
export default {
title: 'Plugins/Azure Devops/Build Table',
component: BuildTable,
};
const buildStatuses: Array<[BuildStatus, BuildResult]> = [
[BuildStatus.InProgress, BuildResult.None], // In Progress
[BuildStatus.Completed, BuildResult.Succeeded], // Succeeded
[BuildStatus.Completed, BuildResult.Failed], // Failed
[BuildStatus.Completed, BuildResult.PartiallySucceeded], // Partially Succeeded
[BuildStatus.Completed, BuildResult.Canceled], // Cancelled
[BuildStatus.Completed, BuildResult.None], // Unknown
[BuildStatus.Cancelling, BuildResult.None], // Cancelling
[BuildStatus.Postponed, BuildResult.None], // Postponed
[BuildStatus.NotStarted, BuildResult.None], // Not Started
[BuildStatus.None, BuildResult.None], // Unknown
];
const generateTestData = (rows = 10): RepoBuild[] => {
const repoBuilds: RepoBuild[] = [];
for (let i = 0; i < rows; i++) {
const [status, result] = buildStatuses[i] ?? [
BuildStatus.Completed,
BuildResult.Succeeded,
];
repoBuilds.push({
id: rows - i + 12534,
title: `backstage ci - 1.0.0-preview-${rows - i}`,
status,
result,
queueTime: new Date(Date.now() - i * 60000),
source: 'refs/heads/main',
link: '',
});
}
return repoBuilds;
};
export const Default = () => (
<MemoryRouter>
<BuildTable items={generateTestData()} loading={false} error={undefined} />
</MemoryRouter>
);
export const Empty = () => (
<MemoryRouter>
<BuildTable items={[]} loading={false} error={undefined} />
</MemoryRouter>
);
export const Loading = () => (
<MemoryRouter>
<BuildTable items={[]} loading error={undefined} />
</MemoryRouter>
);
export const ErrorMessage = () => (
<MemoryRouter>
<BuildTable
items={[]}
loading={false}
error={new Error('Failed to load builds!')}
/>
</MemoryRouter>
);
@@ -149,9 +149,7 @@ const columns: TableColumn[] = [
field: 'queueTime',
width: 'auto',
render: (row: Partial<RepoBuild>) =>
DateTime.fromISO(
row.queueTime ? row.queueTime.toString() : new Date().toString(),
).toRelative(),
DateTime.fromJSDate(row.queueTime ?? new Date()).toRelative(),
},
];
+8 -5
View File
@@ -870,11 +870,14 @@ export type EntityAncestryResponse = {
// Warning: (ae-missing-release-tag) "EntityFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export type EntityFilter = {
anyOf: {
allOf: EntitiesSearchFilter[];
}[];
};
export type EntityFilter =
| {
allOf: EntityFilter[];
}
| {
anyOf: EntityFilter[];
}
| EntitiesSearchFilter;
// Warning: (ae-missing-release-tag) "EntityPagination" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
+4 -3
View File
@@ -22,9 +22,10 @@ import { Entity, EntityRelationSpec } from '@backstage/catalog-model';
* Any (at least one) of the outer sets must match, within which all of the
* individual filters must match.
*/
export type EntityFilter = {
anyOf: { allOf: EntitiesSearchFilter[] }[];
};
export type EntityFilter =
| { allOf: EntityFilter[] }
| { anyOf: EntityFilter[] }
| EntitiesSearchFilter;
/**
* A pagination rule for entities.
@@ -46,7 +46,11 @@ import {
DbPageInfo,
Transaction,
} from './types';
import { EntityPagination } from '../../catalog/types';
import { EntityPagination, EntitiesSearchFilter } from '../../catalog/types';
type LegacyEntityFilter = {
anyOf: { allOf: EntitiesSearchFilter[] }[];
};
// The number of items that are sent per batch to the database layer, when
// doing .batchInsert calls to knex. This needs to be low enough to not cause
@@ -217,13 +221,28 @@ export class CommonDatabase implements Database {
let entitiesQuery = tx<DbEntitiesRow>('entities');
for (const singleFilter of request?.filter?.anyOf ?? []) {
if (
request?.filter &&
(request.filter.hasOwnProperty('key') ||
request.filter.hasOwnProperty('allOf'))
) {
throw new Error(
'Filters for the legacy CommonDatabase must obey the { anyOf: [{ allOf: [] }] } format.',
);
}
for (const singleFilter of (request?.filter as LegacyEntityFilter)?.anyOf ??
[]) {
entitiesQuery = entitiesQuery.orWhere(function singleFilterFn() {
for (const {
key,
matchValueIn,
matchValueExists,
} of singleFilter.allOf) {
for (const filter of singleFilter.allOf) {
if (
filter.hasOwnProperty('anyOf') ||
filter.hasOwnProperty('allOf')
) {
throw new Error(
'Nested filters are not supported in the legacy CommonDatabase',
);
}
const { key, matchValueIn, matchValueExists } = filter;
// NOTE(freben): This used to be a set of OUTER JOIN, which may seem to
// make a lot of sense. However, it had abysmal performance on sqlite
// when datasets grew large, so we're using IN instead.
@@ -23,6 +23,7 @@ import {
DbFinalEntitiesRow,
DbRefreshStateReferencesRow,
DbRefreshStateRow,
DbSearchRow,
} from '../database/tables';
import { NextEntitiesCatalog } from './NextEntitiesCatalog';
@@ -73,6 +74,52 @@ describe('NextEntitiesCatalog', () => {
}
}
async function addEntityToSearch(knex: Knex, entity: Entity) {
const id = uuid();
const entityRef = stringifyEntityRef(entity);
const entityJson = JSON.stringify(entity);
await knex<DbRefreshStateRow>('refresh_state').insert({
entity_id: id,
entity_ref: entityRef,
unprocessed_entity: entityJson,
errors: '[]',
next_update_at: '2031-01-01 23:00:00',
last_discovery_at: '2021-04-01 13:37:00',
});
await knex<DbFinalEntitiesRow>('final_entities').insert({
entity_id: id,
final_entity: entityJson,
hash: 'h',
stitch_ticket: '',
});
await insertSearchRow(knex, id, null, entity);
}
async function insertSearchRow(
knex: Knex,
id: string,
previousKey: string | null,
previousValue: Object,
) {
return Promise.all(
Object.entries(previousValue).map(async ([key, value]) => {
const currentKey = `${previousKey ? `${previousKey}.` : ``}${key}`;
if (typeof value === 'object') {
await insertSearchRow(knex, id, currentKey, value);
} else {
await knex<DbSearchRow>('search').insert({
entity_id: id,
key: currentKey,
value: value,
});
}
}),
);
}
describe('entityAncestry', () => {
it.each(databases.eachSupportedId())(
'should return the ancestry with one parent, %p',
@@ -209,4 +256,106 @@ describe('NextEntitiesCatalog', () => {
60_000,
);
});
describe('entities', () => {
it.each(databases.eachSupportedId())(
'should return correct entity for simple filter',
async databaseId => {
const { knex } = await createDatabase(databaseId);
const entity1: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'one' },
spec: {},
};
const entity2: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'two' },
spec: {
test: 'test value',
},
};
await addEntityToSearch(knex, entity1);
await addEntityToSearch(knex, entity2);
const catalog = new NextEntitiesCatalog(knex);
const testFilter = {
key: 'spec.test',
matchValueExists: true,
};
const request = { filter: testFilter };
const { entities } = await catalog.entities(request);
expect(entities.length).toBe(1);
expect(entities[0]).toEqual(entity2);
},
);
it.each(databases.eachSupportedId())(
'should return correct entity for nested filter',
async databaseId => {
const { knex } = await createDatabase(databaseId);
const entity1: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'one', org: 'a', desc: 'description' },
spec: {},
};
const entity2: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'two', org: 'b', desc: 'description' },
spec: {},
};
const entity3: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'three', org: 'b', color: 'red' },
spec: {},
};
const entity4: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'four', org: 'b', color: 'blue' },
spec: {},
};
await addEntityToSearch(knex, entity1);
await addEntityToSearch(knex, entity2);
await addEntityToSearch(knex, entity3);
await addEntityToSearch(knex, entity4);
const catalog = new NextEntitiesCatalog(knex);
const testFilter1 = {
key: 'metadata.org',
matchValueExists: true,
matchValueIn: ['b'],
};
const testFilter2 = {
key: 'metadata.desc',
matchValueExists: true,
};
const testFilter3 = {
key: 'metadata.color',
matchValueExists: true,
matchValueIn: ['blue'],
};
const request = {
filter: {
allOf: [
testFilter1,
{
anyOf: [testFilter2, testFilter3],
},
],
},
};
const { entities } = await catalog.entities(request);
expect(entities.length).toBe(2);
expect(entities).toContainEqual(entity2);
expect(entities).toContainEqual(entity4);
},
);
});
});
@@ -23,6 +23,8 @@ import {
EntitiesResponse,
EntityAncestryResponse,
EntityPagination,
EntityFilter,
EntitiesSearchFilter,
} from '../catalog/types';
import {
DbFinalEntitiesRow,
@@ -73,6 +75,90 @@ function stringifyPagination(input: { limit: number; offset: number }) {
return base64;
}
function addCondition(
queryBuilder: Knex.QueryBuilder,
db: Knex,
{ key, matchValueIn, matchValueExists }: EntitiesSearchFilter,
) {
// NOTE(freben): This used to be a set of OUTER JOIN, which may seem to
// make a lot of sense. However, it had abysmal performance on sqlite
// when datasets grew large, so we're using IN instead.
const matchQuery = db<DbSearchRow>('search')
.select('entity_id')
.where(function keyFilter() {
this.andWhere({ key: key.toLowerCase() });
if (matchValueExists !== false && matchValueIn) {
if (matchValueIn.length === 1) {
this.andWhere({ value: matchValueIn[0].toLowerCase() });
} else if (matchValueIn.length > 1) {
this.andWhere(
'value',
'in',
matchValueIn.map(v => v.toLowerCase()),
);
}
}
});
// Explicitly evaluate matchValueExists as a boolean since it may be undefined
queryBuilder.andWhere(
'entity_id',
matchValueExists === false ? 'not in' : 'in',
matchQuery,
);
}
function isEntitiesSearchFilter(
filter: EntitiesSearchFilter | EntityFilter,
): filter is EntitiesSearchFilter {
return filter.hasOwnProperty('key');
}
function isAndEntityFilter(
filter: { allOf: EntityFilter[] } | EntityFilter,
): filter is { allOf: EntityFilter[] } {
return filter.hasOwnProperty('allOf');
}
function isOrEntityFilter(
filter: { anyOf: EntityFilter[] } | EntityFilter,
): filter is { anyOf: EntityFilter[] } {
return filter.hasOwnProperty('anyOf');
}
function parseFilter(
filter: EntityFilter,
query: Knex.QueryBuilder,
db: Knex,
): Knex.QueryBuilder {
if (isEntitiesSearchFilter(filter)) {
return query.where(function filterFunction() {
addCondition(this, db, filter);
});
}
if (isOrEntityFilter(filter)) {
let cumulativeQuery = query;
for (const subFilter of filter.anyOf ?? []) {
cumulativeQuery = cumulativeQuery.orWhere(subQuery =>
parseFilter(subFilter, subQuery, db),
);
}
return cumulativeQuery;
}
if (isAndEntityFilter(filter)) {
let cumulativeQuery = query;
for (const subFilter of filter.allOf ?? []) {
cumulativeQuery = cumulativeQuery.andWhere(subQuery =>
parseFilter(subFilter, subQuery, db),
);
}
return cumulativeQuery;
}
return query;
}
export class NextEntitiesCatalog implements EntitiesCatalog {
constructor(private readonly database: Knex) {}
@@ -80,41 +166,8 @@ export class NextEntitiesCatalog implements EntitiesCatalog {
const db = this.database;
let entitiesQuery = db<DbFinalEntitiesRow>('final_entities');
for (const singleFilter of request?.filter?.anyOf ?? []) {
entitiesQuery = entitiesQuery.orWhere(function singleFilterFn() {
for (const {
key,
matchValueIn,
matchValueExists,
} of singleFilter.allOf) {
// NOTE(freben): This used to be a set of OUTER JOIN, which may seem to
// make a lot of sense. However, it had abysmal performance on sqlite
// when datasets grew large, so we're using IN instead.
const matchQuery = db<DbSearchRow>('search')
.select('entity_id')
.where(function keyFilter() {
this.andWhere({ key: key.toLowerCase() });
if (matchValueExists !== false && matchValueIn) {
if (matchValueIn.length === 1) {
this.andWhere({ value: matchValueIn[0].toLowerCase() });
} else if (matchValueIn.length > 1) {
this.andWhere(
'value',
'in',
matchValueIn.map(v => v.toLowerCase()),
);
}
}
});
// Explicitly evaluate matchValueExists as a boolean since it may be undefined
this.andWhere(
'entity_id',
matchValueExists === false ? 'not in' : 'in',
matchQuery,
);
}
});
if (request?.filter) {
entitiesQuery = parseFilter(request.filter, entitiesQuery, db);
}
// TODO: move final_entities to use entity_ref
+12 -2
View File
@@ -15,6 +15,7 @@ import { Context } from 'react';
import { Entity } from '@backstage/catalog-model';
import { EntityName } from '@backstage/catalog-model';
import { IconButton } from '@material-ui/core';
import { IdentityApi } from '@backstage/core-plugin-api';
import { LinkProps } from '@backstage/core-components';
import { Observable } from '@backstage/types';
import { PropsWithChildren } from 'react';
@@ -736,6 +737,17 @@ export function getEntitySourceLocation(
// @public
export function isOwnerOf(owner: Entity, owned: Entity): boolean;
// @public
export function loadCatalogOwnerRefs(
catalogApi: CatalogApi,
identityOwnerRefs: string[],
): Promise<string[]>;
// @public
export function loadIdentityOwnerRefs(
identityApi: IdentityApi,
): Promise<string[]>;
// Warning: (ae-missing-release-tag) "MockEntityListContextProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -823,8 +835,6 @@ export function useEntityListProvider<
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,
>(): EntityListContextProps<EntityFilters>;
// Warning: (ae-missing-release-tag) "useEntityOwnership" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export function useEntityOwnership(): {
loading: boolean;
+5 -1
View File
@@ -37,4 +37,8 @@ export { useOwnUser } from './useOwnUser';
export { useRelatedEntities } from './useRelatedEntities';
export { useStarredEntities } from './useStarredEntities';
export { useStarredEntity } from './useStarredEntity';
export { useEntityOwnership } from './useEntityOwnership';
export {
loadCatalogOwnerRefs,
useEntityOwnership,
loadIdentityOwnerRefs,
} from './useEntityOwnership';
@@ -49,9 +49,16 @@ function extendUserId(id: string): string {
}
}
// Takes the relevant parts of the Backstage identity, and translates them into
// a list of entity refs on string form that represent the user's ownership
// connections.
/**
* Takes the relevant parts of the Backstage identity, and translates them into
* a list of entity refs on string form that represent the user's ownership
* connections.
*
* @public
*
* @param identityApi - The IdentityApi implementation
* @returns IdentityOwner refs as a string array
*/
export async function loadIdentityOwnerRefs(
identityApi: IdentityApi,
): Promise<string[]> {
@@ -81,9 +88,17 @@ export async function loadIdentityOwnerRefs(
return result;
}
// Takes the relevant parts of the User entity corresponding to the Backstage
// identity, and translates them into a list of entity refs on string form that
// represent the user's ownership connections.
/**
* Takes the relevant parts of the User entity corresponding to the Backstage
* identity, and translates them into a list of entity refs on string form that
* represent the user's ownership connections.
*
* @public
*
* @param catalogApi - The Catalog API implementation
* @param identityOwnerRefs - List of identity owner refs as strings
* @returns OwnerRefs as a string array
*/
export async function loadCatalogOwnerRefs(
catalogApi: CatalogApi,
identityOwnerRefs: string[],
@@ -113,6 +128,10 @@ export async function loadCatalogOwnerRefs(
* owner of a given entity. When the hook is initially mounted, the loading
* flag will be true and the results returned from the function will always be
* false.
*
* @public
*
* @returns a function that checks if the signed in user owns an entity
*/
export function useEntityOwnership(): {
loading: boolean;
+1
View File
@@ -33,6 +33,7 @@
"@material-ui/lab": "4.0.0-alpha.57",
"@material-ui/styles": "^4.11.0",
"highlight.js": "^10.6.0",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
@@ -46,6 +46,8 @@ import { codeCoverageApiRef } from '../../api';
import { Progress, ResponseErrorPanel } from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import { DateTime } from 'luxon';
type Coverage = 'line' | 'branch';
const useStyles = makeStyles<BackstageTheme>(theme => ({
@@ -69,6 +71,13 @@ const getTrendIcon = (trend: number, classes: ClassNameMap) => {
}
};
// convert timestamp to human friendly form
function formatDateToHuman(timeStamp: string | number) {
return DateTime.fromMillis(Number(timeStamp)).toLocaleString(
DateTime.DATETIME_MED,
);
}
export const CoverageHistoryChart = () => {
const { entity } = useEntity();
const codeCoverageApi = useApi(codeCoverageApiRef);
@@ -149,10 +158,10 @@ export const CoverageHistoryChart = () => {
margin={{ right: 48, top: 32 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="timestamp" />
<XAxis dataKey="timestamp" tickFormatter={formatDateToHuman} />
<YAxis dataKey="line.percentage" />
<YAxis dataKey="branch.percentage" />
<Tooltip />
<Tooltip labelFormatter={formatDateToHuman} />
<Legend />
<Line
type="monotone"
@@ -232,7 +232,7 @@ export const FileExplorer = () => {
title: 'Coverage',
type: 'numeric',
field: 'coverage',
render: (row: CoverageTableRow) => `${row.coverage}%`,
render: (row: CoverageTableRow) => `${row.coverage.toFixed(2)}%`,
},
{
title: 'Missing lines',
@@ -22,7 +22,7 @@
"dependencies": {
"@backstage/config": "^0.1.8",
"@backstage/search-common": "^0.2.0",
"@elastic/elasticsearch": "^7.13.0",
"@elastic/elasticsearch": "7.13.0",
"@acuris/aws-es-connection": "^2.2.0",
"aws-sdk": "^2.948.0",
"elastic-builder": "^2.16.0",
+200
View File
@@ -0,0 +1,200 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-extraneous-dependencies */
import { Octokit } from '@octokit/rest';
import minimist from 'minimist';
// Generated by GitHub workflow .github/workflows/snyk-github-issue-creator
import synkJsonOutput from '../snyk.json';
type Vulnerability = {
description: string;
packages: {
name: string;
target: string;
}[];
snykId: string;
};
const argv = minimist(process.argv.slice(2));
const GH_OWNER = 'backstage';
const GH_REPO = 'backstage';
const SNYK_GH_LABEL = 'snyk-vulnerability';
const SNYK_ID_REGEX = /\[([A-Z0-9-:]+)]/i;
const isDryRun = 'dryrun' in argv;
if (!process.env.GITHUB_TOKEN) {
console.error('GITHUB_TOKEN is not set. Please provide a Github token');
process.exit(1);
}
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
if (isDryRun) {
console.log(
'⚠️ Running in dryrun mode, no issues will be updated on Github ⚠️',
);
}
const fetchSnykGithubIssueMap = async (): Promise<Record<string, number>> => {
const snykGithubIssueMap: Record<string, number> = {};
const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
owner: GH_OWNER,
repo: GH_REPO,
per_page: 100,
state: 'open',
labels: SNYK_GH_LABEL,
});
for await (const { data: issues } of iterator) {
for (const issue of issues) {
// Gets the Vulnerability ID from square braces
const match = SNYK_ID_REGEX.exec(issue.title);
if (match && match[1]) {
snykGithubIssueMap[match[1]] = issue.number;
} else {
console.log(`Unmatched Snyk ID for ${issue.title}`);
}
}
}
return snykGithubIssueMap;
};
const generateIssueBody = (vulnerability: Vulnerability) => `
## Affecting Packages/Plugins
${Array.from(vulnerability.packages).map(
({ name, target }) => `* [${name}](${target})\n`,
)}
${vulnerability.description}
`;
const createGithubIssue = async (vulnerability: Vulnerability) => {
console.log(
`Create Github Issue for Snyk Vulnerability ${vulnerability.snykId}`,
);
vulnerability.packages.forEach(({ name, target }) => {
console.log(`- ${name} [${target}]`);
});
if (!isDryRun) {
await octokit.issues.create({
owner: GH_OWNER,
repo: GH_REPO,
title: `Snyk vulnerability [${vulnerability.snykId}]`,
labels: [SNYK_GH_LABEL, 'help wanted'],
body: generateIssueBody(vulnerability),
});
}
};
const updateGithubIssue = async (
githubIssueId: number,
vulnerability: Vulnerability,
) => {
console.log(
`Update Github Issue #${githubIssueId} for Snky Vulnerability ${vulnerability.snykId}`,
);
if (!isDryRun) {
await octokit.issues.update({
owner: GH_OWNER,
repo: GH_REPO,
issue_number: githubIssueId,
body: generateIssueBody(vulnerability),
});
}
};
const closeGithubIssue = async (githubIssueId: number) => {
console.log(`Closing Github Issue #${githubIssueId}`);
if (!isDryRun) {
await octokit.issues.update({
owner: GH_OWNER,
repo: GH_REPO,
issue_number: githubIssueId,
state: 'closed',
});
}
};
async function main() {
const snykGithubIssueMap = await fetchSnykGithubIssueMap();
const vulnerabilityStore: Record<string, Vulnerability> = {};
// Group the Snyk vulnerabilities, and link back to the affecting packages.
synkJsonOutput.forEach(
({ projectName, displayTargetFile, vulnerabilities }) => {
vulnerabilities.forEach(
({ id, description }: { id: string; description: string }) => {
if (id !== undefined && description !== undefined) {
if (vulnerabilityStore[id]) {
if (
!vulnerabilityStore[id].packages.some(
({ name }) => name === projectName,
)
) {
vulnerabilityStore[id].packages.push({
name: projectName,
target: displayTargetFile,
});
}
} else {
vulnerabilityStore[id] = {
description,
snykId: id,
packages: [
{
name: projectName,
target: displayTargetFile,
},
],
};
}
}
},
);
},
);
for (const [id, vulnerability] of Object.entries(vulnerabilityStore)) {
if (snykGithubIssueMap[id]) {
await updateGithubIssue(snykGithubIssueMap[id], vulnerability);
} else {
await createGithubIssue(vulnerability);
}
}
for (const [snykId, githubIssueId] of Object.entries(snykGithubIssueMap)) {
if (!vulnerabilityStore[snykId]) {
await closeGithubIssue(githubIssueId);
}
}
}
main().catch(error => {
console.error(error.stack);
process.exit(1);
});
+137 -93
View File
@@ -2712,7 +2712,7 @@
find-my-way "^2.2.2"
into-stream "^5.1.1"
"@elastic/elasticsearch@^7.13.0":
"@elastic/elasticsearch@7.13.0":
version "7.13.0"
resolved "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-7.13.0.tgz#6dcf511dfa91187e22c81e54f41f4bd0fd96b4d6"
integrity sha512-WgwLWo2p9P2tdqzBGX9fHeG8p5IOTXprXNTECQG2mJ7z9n93N5AFBJpEw4d35tWWeCWi9jI13A2wzQZH7XZ/xw==
@@ -2968,16 +2968,14 @@
stream-events "^1.0.1"
xdg-basedir "^4.0.0"
"@graphiql/toolkit@^0.2.0":
version "0.2.2"
resolved "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.2.2.tgz#193d570afcf686c9ee61c92054c1782b9f3c1255"
integrity sha512-kDgYhqnS4p4LqSo1KvLd3tbX8Hhdj0ZrgQuGsosjjEnahiPYmmylxUL1p9lj6348OsypcTlCncGpEjeb9S3TiQ==
"@graphiql/toolkit@^0.3.2":
version "0.3.2"
resolved "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.3.2.tgz#551753436ada2bc27ea870b7668e5199a958ccfb"
integrity sha512-IweIT9VC8uDovg7kuCO9YqZcnIuWU8IGzrpUisXv6CUNK2Ed1ke8yERDTMmF/rjvLd2DeVZwM8iEOjEs4sUJQw==
dependencies:
"@n1ru4l/push-pull-async-iterable-iterator" "^2.1.4"
graphql-ws "^4.3.2"
"@n1ru4l/push-pull-async-iterable-iterator" "^3.0.0"
graphql-ws "^4.9.0"
meros "^1.1.4"
optionalDependencies:
subscriptions-transport-ws "^0.9.18"
"@graphql-codegen/cli@^1.21.3":
version "1.21.6"
@@ -4635,10 +4633,10 @@
outvariant "^1.2.0"
strict-event-emitter "^0.2.0"
"@n1ru4l/push-pull-async-iterable-iterator@^2.1.4":
version "2.1.4"
resolved "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-2.1.4.tgz#a90225474352f9f159bff979905f707b9c6bcf04"
integrity sha512-qLIvoOUJ+zritv+BlzcBMePKNjKQzH9Rb2i9W98YXxf/M62Lye8qH0peyiU8yJ1tL0kfulWi31BoK10E6BKJeA==
"@n1ru4l/push-pull-async-iterable-iterator@^3.0.0":
version "3.1.0"
resolved "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.1.0.tgz#be450c97d1c7cd6af1a992d53232704454345df9"
integrity sha512-K4scWxGhdQM0masHHy4gIQs2iGiLEXCrXttumknyPJqtdl4J179BjpibWSSQ1fxKdCcHgIlCTKXJU6cMM6D6Wg==
"@nodelib/fs.scandir@2.1.3":
version "2.1.3"
@@ -4893,6 +4891,19 @@
before-after-hook "^2.1.0"
universal-user-agent "^6.0.0"
"@octokit/core@^3.5.1":
version "3.5.1"
resolved "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b"
integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==
dependencies:
"@octokit/auth-token" "^2.4.4"
"@octokit/graphql" "^4.5.8"
"@octokit/request" "^5.6.0"
"@octokit/request-error" "^2.0.5"
"@octokit/types" "^6.0.3"
before-after-hook "^2.2.0"
universal-user-agent "^6.0.0"
"@octokit/endpoint@^6.0.1":
version "6.0.3"
resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.3.tgz#dd09b599662d7e1b66374a177ab620d8cdf73487"
@@ -4927,6 +4938,11 @@
"@octokit/types" "^6.12.2"
btoa-lite "^1.0.0"
"@octokit/openapi-types@^11.2.0":
version "11.2.0"
resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz#b38d7fc3736d52a1e96b230c1ccd4a58a2f400a6"
integrity sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==
"@octokit/openapi-types@^7.3.2":
version "7.3.2"
resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.3.2.tgz#065ce49b338043ec7f741316ce06afd4d459d944"
@@ -4937,6 +4953,13 @@
resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437"
integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==
"@octokit/plugin-paginate-rest@^2.16.8":
version "2.17.0"
resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz#32e9c7cab2a374421d3d0de239102287d791bce7"
integrity sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==
dependencies:
"@octokit/types" "^6.34.0"
"@octokit/plugin-paginate-rest@^2.6.2":
version "2.7.0"
resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.7.0.tgz#6bb7b043c246e0654119a6ec4e72a172c9e2c7f3"
@@ -4949,6 +4972,11 @@
resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz#394d59ec734cd2f122431fbaf05099861ece3c44"
integrity sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg==
"@octokit/plugin-request-log@^1.0.4":
version "1.0.4"
resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85"
integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==
"@octokit/plugin-rest-endpoint-methods@5.3.1":
version "5.3.1"
resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.3.1.tgz#deddce769b4ec3179170709ab42e4e9e6195aaa9"
@@ -4957,6 +4985,14 @@
"@octokit/types" "^6.16.2"
deprecation "^2.3.1"
"@octokit/plugin-rest-endpoint-methods@^5.12.0":
version "5.13.0"
resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz#8c46109021a3412233f6f50d28786f8e552427ba"
integrity sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==
dependencies:
"@octokit/types" "^6.34.0"
deprecation "^2.3.1"
"@octokit/request-error@^2.0.0", "@octokit/request-error@^2.0.2", "@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0":
version "2.1.0"
resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677"
@@ -4988,6 +5024,16 @@
"@octokit/plugin-request-log" "^1.0.2"
"@octokit/plugin-rest-endpoint-methods" "5.3.1"
"@octokit/rest@^18.12.0":
version "18.12.0"
resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz#f06bc4952fc87130308d810ca9d00e79f6988881"
integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==
dependencies:
"@octokit/core" "^3.5.1"
"@octokit/plugin-paginate-rest" "^2.16.8"
"@octokit/plugin-request-log" "^1.0.4"
"@octokit/plugin-rest-endpoint-methods" "^5.12.0"
"@octokit/types@^5.0.0", "@octokit/types@^5.0.1":
version "5.5.0"
resolved "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b"
@@ -5002,6 +5048,13 @@
dependencies:
"@octokit/openapi-types" "^7.3.2"
"@octokit/types@^6.34.0":
version "6.34.0"
resolved "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz#c6021333334d1ecfb5d370a8798162ddf1ae8218"
integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==
dependencies:
"@octokit/openapi-types" "^11.2.0"
"@octokit/webhooks-methods@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz#1108b9ea661ca6c81e4a8bfa63a09eb27d5bc2db"
@@ -9898,6 +9951,11 @@ before-after-hook@^2.1.0:
resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
before-after-hook@^2.2.0:
version "2.2.2"
resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==
better-opn@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/better-opn/-/better-opn-2.1.1.tgz#94a55b4695dc79288f31d7d0e5f658320759f7c6"
@@ -11076,18 +11134,18 @@ code-point-at@^1.0.0:
resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
codemirror-graphql@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-1.0.0.tgz#ba8db60dc42b87768d643b3d19bf088f43dc5380"
integrity sha512-6LnSeRldL7psIBfjDr4xXKxCqPVYfQE4Yj04p2VpIyAIpc4MVE4VOjzvILgnmAW8X93ou5/s5gQXvB4huDwTUQ==
codemirror-graphql@^1.0.3:
version "1.1.0"
resolved "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-1.1.0.tgz#dd22ddf7761efa9131fa99a70a4a85fe653484e5"
integrity sha512-bp2XUg7epL07kJcylM8VCISK6X+rFsHL2lUkPQAw2v721MVhn+80FgjMP8tiZCOfJgHn1+JgsA71L5nOHWgUdA==
dependencies:
graphql-language-service-interface "^2.8.2"
graphql-language-service-parser "^1.9.0"
graphql-language-service-interface "^2.9.0"
graphql-language-service-parser "^1.10.0"
codemirror@^5.54.0:
version "5.59.4"
resolved "https://registry.npmjs.org/codemirror/-/codemirror-5.59.4.tgz#bfc11c8ce32b04818e8d661bbd790a94f4b3a6f3"
integrity sha512-achw5JBgx8QPcACDDn+EUUXmCYzx/zxEtOGXyjvLEvYY8GleUrnfm5D+Zb+UjShHggXKDT9AXrbkBZX6a0YSQg==
codemirror@^5.58.2:
version "5.63.3"
resolved "https://registry.npmjs.org/codemirror/-/codemirror-5.63.3.tgz#97042a242027fe0c87c09b36bc01931d37b76527"
integrity sha512-1C+LELr+5grgJYqwZKqxrcbPsHFHapVaVAloBsFBASbpLnQqLw1U8yXJ3gT5D+rhxIiSpo+kTqN+hQ+9ialIXw==
codeowners-utils@^1.0.2:
version "1.0.2"
@@ -13358,7 +13416,12 @@ entities@^1.1.1, entities@^1.1.2:
resolved "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
entities@^2.0.0, entities@~2.0.0:
entities@^2.0.0, entities@~2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
entities@~2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
@@ -14372,11 +14435,6 @@ fecha@^4.2.0:
resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41"
integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==
fetch-blob@2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c"
integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==
fetch-readablestream@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz#eaa6d1a76b12de2d4731a343393c6ccdcfe2c795"
@@ -15486,18 +15544,19 @@ grapheme-splitter@^1.0.4:
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
graphiql@^1.0.0-alpha.10:
version "1.4.2"
resolved "https://registry.npmjs.org/graphiql/-/graphiql-1.4.2.tgz#a1dc1a4d8d35f60c90d6d8a9eb62a99756e9fd9b"
integrity sha512-TQDuuU/ZqTWV1yQDpVEiKskg0IYA+Wck37DYrrFzLlpgZWRbWiyab1PyHKiRep7J540CgScBg6C/gGCymKyO3g==
version "1.4.7"
resolved "https://registry.npmjs.org/graphiql/-/graphiql-1.4.7.tgz#6a35acf0786d7518fbb986b75bf0a3d752c19c1a"
integrity sha512-oHsBTzdWTbRJhqazbjrC6wY7YInViErAeXLqetCxdFFu2Zk5FV3V3rs7KPrCyr7kM6lW0nfXMzIfKuIgxAqx7g==
dependencies:
"@graphiql/toolkit" "^0.2.0"
codemirror "^5.54.0"
codemirror-graphql "^1.0.0"
"@graphiql/toolkit" "^0.3.2"
codemirror "^5.58.2"
codemirror-graphql "^1.0.3"
copy-to-clipboard "^3.2.0"
dset "^3.1.0"
entities "^2.0.0"
graphql-language-service "^3.1.2"
markdown-it "^10.0.0"
escape-html "^1.0.3"
graphql-language-service "^3.1.6"
markdown-it "^12.2.0"
graphlib@^2.1.8:
version "2.1.8"
@@ -15532,7 +15591,7 @@ graphql-extensions@^0.15.0:
apollo-server-env "^3.1.0"
apollo-server-types "^0.9.0"
graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2.8.2:
graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2.9.0:
version "2.8.2"
resolved "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.2.tgz#b3bb2aef7eaf0dff0b4ea419fa412c5f66fa268b"
integrity sha512-otbOQmhgkAJU1QJgQkMztNku6SbJLu/uodoFOYOOtJsizTjrMs93vkYaHCcYnLA3oi1Goj27XcHjMnRCYQOZXQ==
@@ -15542,7 +15601,7 @@ graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2.
graphql-language-service-utils "^2.5.1"
vscode-languageserver-types "^3.15.1"
graphql-language-service-parser@1.9.0, graphql-language-service-parser@^1.9.0:
graphql-language-service-parser@1.9.0, graphql-language-service-parser@^1.10.0, graphql-language-service-parser@^1.9.0:
version "1.9.0"
resolved "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.0.tgz#79af21294119a0a7e81b6b994a1af36833bab724"
integrity sha512-B5xPZLbBmIp0kHvpY1Z35I5DtPoDK9wGxQVRDIzcBaiIvAmlTrDvjo3bu7vKREdjFbYKvWNgrEWENuprMbF17Q==
@@ -15554,6 +15613,11 @@ graphql-language-service-types@^1.8.0:
resolved "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.1.tgz#963810010924f2b5eaea415d5b8eb0b7d42c479b"
integrity sha512-IpYS0mEHEmRsFlq+loWCpSYYYizAID7Alri6GoFN1QqUdux+8rp1Tkp2NGsGDpDmm3Dbz5ojmJWzNWQGpuwveA==
graphql-language-service-types@^1.8.2:
version "1.8.2"
resolved "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.2.tgz#50ae56f69cc24fcfc3daa129b68b0eb9421e8578"
integrity sha512-Sj07RHnMwAhEvAt7Jdt1l/x56ZpoNh+V6g+T58CF6GiYqI5l4vXqqRB4d4xHDcNQX98GpJfnf3o8BqPgP3C5Sw==
graphql-language-service-utils@^2.5.1:
version "2.5.1"
resolved "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.1.tgz#832ad4b0a9da03fdded756932c27e057ccf71302"
@@ -15562,13 +15626,23 @@ graphql-language-service-utils@^2.5.1:
graphql-language-service-types "^1.8.0"
nullthrows "^1.0.0"
graphql-language-service@^3.1.2:
version "3.1.2"
resolved "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-3.1.2.tgz#6f50d5d824ea09c402cb02902b10e54b9da899d5"
integrity sha512-OiOH8mVE+uotrl3jGA2Pgt9k7rrI8lgw/8p+Cf6nwyEHbmIZj37vX9KoOWgpdFhuQlw824nNxWHSbz6k90xjWQ==
graphql-language-service-utils@^2.5.3:
version "2.5.3"
resolved "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.3.tgz#185f4f65cf8c010871eb9405452a3a0bfdf88748"
integrity sha512-ydevEZ0AgzEKQF3hiCbLXuS0o7189Ww/T30WtCKCLaRHDYk9Yyb2PZWdhSTWLxYZTaX2TccV6NtFWvzIC7UP3g==
dependencies:
graphql-language-service-interface "^2.8.2"
graphql-language-service-types "^1.8.0"
nullthrows "^1.0.0"
graphql-language-service@^3.1.6:
version "3.2.0"
resolved "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-3.2.0.tgz#e0eb6d5dea2cab92549a253d7a6b4fa0cce178b7"
integrity sha512-xM5Ua5p7ttG/oEaDy2zk35FP2O2I9qD2N0DOrjCDUVDRC06FNDG+/CvF4qX9+i8DWOI65xch5vAhSQEfS2jFsA==
dependencies:
graphql-language-service-interface "^2.9.0"
graphql-language-service-parser "^1.10.0"
graphql-language-service-types "^1.8.2"
graphql-language-service-utils "^2.5.3"
graphql-request@^3.3.0:
version "3.4.0"
@@ -15623,16 +15697,16 @@ graphql-type-json@^0.3.2:
resolved "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115"
integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==
graphql-ws@^4.3.2:
version "4.9.0"
resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz#5cfd8bb490b35e86583d8322f5d5d099c26e365c"
integrity sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag==
graphql-ws@^4.4.1:
version "4.7.0"
resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.7.0.tgz#b323fbf35a3736eed85dac24c0054d6d10c93e62"
integrity sha512-Md8SsmC9ZlsogFPd3Ot8HbIAAqsHh8Xoq7j4AmcIat1Bh6k91tjVyQvA0Au1/BolXSYq+RDvib6rATU2Hcf1Xw==
graphql-ws@^4.9.0:
version "4.9.0"
resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz#5cfd8bb490b35e86583d8322f5d5d099c26e365c"
integrity sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag==
graphql@15.5.0:
version "15.5.0"
resolved "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5"
@@ -18761,13 +18835,6 @@ lines-and-columns@^1.1.6:
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
linkify-it@^2.0.0:
version "2.2.0"
resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==
dependencies:
uc.micro "^1.0.1"
linkify-it@^3.0.1:
version "3.0.2"
resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8"
@@ -19463,17 +19530,6 @@ markdown-escapes@^1.0.0:
resolved "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
markdown-it@^10.0.0:
version "10.0.0"
resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"
integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==
dependencies:
argparse "^1.0.7"
entities "~2.0.0"
linkify-it "^2.0.0"
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdown-it@^11.0.1:
version "11.0.1"
resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.1.tgz#b54f15ec2a2193efa66dda1eb4173baea08993d6"
@@ -19485,6 +19541,17 @@ markdown-it@^11.0.1:
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdown-it@^12.2.0:
version "12.2.0"
resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz#091f720fd5db206f80de7a8d1f1a7035fd0d38db"
integrity sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==
dependencies:
argparse "^2.0.1"
entities "~2.1.0"
linkify-it "^3.0.1"
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdown-table@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b"
@@ -22056,9 +22123,9 @@ passport-oauth2@1.2.0:
uid2 "0.0.x"
passport-oauth2@1.x.x, passport-oauth2@^1.1.2, passport-oauth2@^1.4.0, passport-oauth2@^1.5.0:
version "1.6.0"
resolved "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.0.tgz#5f599735e0ea40ea3027643785f81a3a9b4feb50"
integrity sha512-emXPLqLcVEcLFR/QvQXZcwLmfK8e9CqvMgmOFJxcNT3okSFMtUbRRKpY20x5euD+01uHsjjCa07DYboEeLXYiw==
version "1.6.1"
resolved "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.1.tgz#c5aee8f849ce8bd436c7f81d904a3cd1666f181b"
integrity sha512-ZbV43Hq9d/SBSYQ22GOiglFsjsD1YY/qdiptA+8ej+9C1dL1TVB+mBE5kDH/D4AJo50+2i8f4bx0vg4/yDDZCQ==
dependencies:
base64url "3.x.x"
oauth "0.9.x"
@@ -28523,11 +28590,6 @@ webidl-conversions@^3.0.0:
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
webidl-conversions@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
@@ -28751,24 +28813,6 @@ whatwg-url@^5.0.0:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
whatwg-url@^6.4.1:
version "6.5.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
dependencies:
lodash.sortby "^4.7.0"
tr46 "^1.0.1"
webidl-conversions "^4.0.2"
whatwg-url@^7.0.0:
version "7.1.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
dependencies:
lodash.sortby "^4.7.0"
tr46 "^1.0.1"
webidl-conversions "^4.0.2"
whatwg-url@^8.0.0, whatwg-url@^8.4.0:
version "8.4.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837"