From 83b6e0c1fc347e9d86fcf7435c005fe47a8488bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 20 Nov 2020 16:30:57 +0100 Subject: [PATCH 01/19] catalog: remove group ancestors and descendants --- .changeset/silent-boxes-exercise.md | 8 ++ .../software-catalog/descriptor-format.md | 40 ---------- .../examples/acme/backstage-group.yaml | 2 - .../examples/acme/boxoffice-group.yaml | 2 - .../examples/acme/infrastructure-group.yaml | 2 - packages/catalog-model/examples/acme/org.yaml | 3 - .../examples/acme/team-a-group.yaml | 2 - .../examples/acme/team-b-group.yaml | 2 - .../examples/acme/team-c-group.yaml | 2 - .../examples/acme/team-d-group.yaml | 2 - .../src/kinds/GroupEntityV1alpha1.test.ts | 42 ---------- .../src/kinds/GroupEntityV1alpha1.ts | 26 ------ .../BuiltinKindsEntityProcessor.test.ts | 30 ------- .../processors/BuiltinKindsEntityProcessor.ts | 19 ----- .../ingestion/processors/ldap/read.test.ts | 4 +- .../src/ingestion/processors/ldap/read.ts | 2 - .../processors/microsoftGraph/read.test.ts | 15 ---- .../processors/microsoftGraph/read.ts | 10 +-- .../ingestion/processors/util/github.test.ts | 2 - .../src/ingestion/processors/util/github.ts | 2 - .../src/ingestion/processors/util/org.test.ts | 34 +++----- .../src/ingestion/processors/util/org.ts | 80 +++++-------------- 22 files changed, 45 insertions(+), 286 deletions(-) create mode 100644 .changeset/silent-boxes-exercise.md diff --git a/.changeset/silent-boxes-exercise.md b/.changeset/silent-boxes-exercise.md new file mode 100644 index 0000000000..0d2003cec9 --- /dev/null +++ b/.changeset/silent-boxes-exercise.md @@ -0,0 +1,8 @@ +--- +'@backstage/catalog-model': minor +'@backstage/plugin-catalog-backend': minor +--- + +Remove the deprecated fields `ancestors` and `descendants` from the `Group` entity. + +See https://github.com/backstage/backstage/issues/3049 and the PRs linked from it for details. diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 438f874990..7e3e774d63 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -723,9 +723,7 @@ metadata: spec: type: business-unit parent: ops - ancestors: [ops, global-synergies, acme-corp] children: [backstage, other] - descendants: [backstage, other, team-a, team-b, team-c, team-d] ``` In addition to the [common envelope metadata](#common-to-all-kinds-the-metadata) @@ -761,25 +759,6 @@ namespace as the user. Only `Group` entities may be referenced. Most commonly, this field points to a group in the same namespace, so in those cases it is sufficient to enter only the `metadata.name` field of that group. -### `spec.ancestors` [required] - -**NOTE**: This field was marked for deprecation on Nov 22nd, 2020. It will be -removed entirely from the model on Dec 6th, 2020 in the repository and will not -be present in released packages following the next release after that. Please -update your code to not consume this field before the removal date. - -The recursive list of parents up the hierarchy, by stepping through parents one -by one. The list must be present, but may be empty if `parent` is not present. -The first entry in the list is equal to `parent`, and then the following ones -are progressively farther up the hierarchy. - -The entries of this array are -[entity references](https://backstage.io/docs/features/software-catalog/references), -with the default kind `Group` and the default namespace equal to the same -namespace as the user. Only `Group` entities may be referenced. Most commonly, -these entries point to groups in the same namespace, so in those cases it is -sufficient to enter only the `metadata.name` field of those groups. - ### `spec.children` [required] The immediate child groups of this group in the hierarchy (whose `parent` field @@ -794,25 +773,6 @@ namespace as the user. Only `Group` entities may be referenced. Most commonly, these entries point to groups in the same namespace, so in those cases it is sufficient to enter only the `metadata.name` field of those groups. -### `spec.descendants` [required] - -**NOTE**: This field was marked for deprecation on Nov 22nd, 2020. It will be -removed entirely from the model on Dec 6th, 2020 in the repository and will not -be present in released packages following the next release after that. Please -update your code to not consume this field before the removal date. - -The immediate and recursive child groups of this group in the hierarchy -(children, and children's children, etc.). The list must be present, but may be -empty if there are no child groups. The items are not guaranteed to be ordered -in any particular way. - -The entries of this array are -[entity references](https://backstage.io/docs/features/software-catalog/references), -with the default kind `Group` and the default namespace equal to the same -namespace as the user. Only `Group` entities may be referenced. Most commonly, -these entries point to groups in the same namespace, so in those cases it is -sufficient to enter only the `metadata.name` field of those groups. - ## Kind: User Describes the following entity kind: diff --git a/packages/catalog-model/examples/acme/backstage-group.yaml b/packages/catalog-model/examples/acme/backstage-group.yaml index f992d155bc..be5baf091e 100644 --- a/packages/catalog-model/examples/acme/backstage-group.yaml +++ b/packages/catalog-model/examples/acme/backstage-group.yaml @@ -6,6 +6,4 @@ metadata: spec: type: sub-department parent: infrastructure - ancestors: [infrastructure, acme-corp] children: [team-a, team-b] - descendants: [team-a, team-b] diff --git a/packages/catalog-model/examples/acme/boxoffice-group.yaml b/packages/catalog-model/examples/acme/boxoffice-group.yaml index 0be1fe58cb..37e9816b50 100644 --- a/packages/catalog-model/examples/acme/boxoffice-group.yaml +++ b/packages/catalog-model/examples/acme/boxoffice-group.yaml @@ -6,6 +6,4 @@ metadata: spec: type: sub-department parent: infrastructure - ancestors: [infrastructure, acme-corp] children: [team-c, team-d] - descendants: [team-c, team-d] diff --git a/packages/catalog-model/examples/acme/infrastructure-group.yaml b/packages/catalog-model/examples/acme/infrastructure-group.yaml index 2341782944..45bad12b7e 100644 --- a/packages/catalog-model/examples/acme/infrastructure-group.yaml +++ b/packages/catalog-model/examples/acme/infrastructure-group.yaml @@ -6,6 +6,4 @@ metadata: spec: type: department parent: acme-corp - ancestors: [acme-corp] children: [backstage, boxoffice] - descendants: [backstage, boxoffice, team-a, team-b, team-c, team-d] diff --git a/packages/catalog-model/examples/acme/org.yaml b/packages/catalog-model/examples/acme/org.yaml index 8c562aaf89..21a890a1aa 100644 --- a/packages/catalog-model/examples/acme/org.yaml +++ b/packages/catalog-model/examples/acme/org.yaml @@ -5,10 +5,7 @@ metadata: description: The acme-corp organization spec: type: organization - ancestors: [] children: [infrastructure] - descendants: - [infrastructure, backstage, boxoffice, team-a, team-b, team-c, team-d] --- apiVersion: backstage.io/v1alpha1 kind: Location diff --git a/packages/catalog-model/examples/acme/team-a-group.yaml b/packages/catalog-model/examples/acme/team-a-group.yaml index dacc4c6c3c..a22559c38b 100644 --- a/packages/catalog-model/examples/acme/team-a-group.yaml +++ b/packages/catalog-model/examples/acme/team-a-group.yaml @@ -6,9 +6,7 @@ metadata: spec: type: team parent: backstage - ancestors: [backstage, infrastructure, acme-corp] children: [] - descendants: [] --- apiVersion: backstage.io/v1alpha1 kind: User diff --git a/packages/catalog-model/examples/acme/team-b-group.yaml b/packages/catalog-model/examples/acme/team-b-group.yaml index 00e9e41d80..0c6604ff79 100644 --- a/packages/catalog-model/examples/acme/team-b-group.yaml +++ b/packages/catalog-model/examples/acme/team-b-group.yaml @@ -6,9 +6,7 @@ metadata: spec: type: team parent: backstage - ancestors: [backstage, infrastructure, acme-corp] children: [] - descendants: [] --- apiVersion: backstage.io/v1alpha1 kind: User diff --git a/packages/catalog-model/examples/acme/team-c-group.yaml b/packages/catalog-model/examples/acme/team-c-group.yaml index 0f97f69cb3..ccc95b7f60 100644 --- a/packages/catalog-model/examples/acme/team-c-group.yaml +++ b/packages/catalog-model/examples/acme/team-c-group.yaml @@ -6,9 +6,7 @@ metadata: spec: type: team parent: boxoffice - ancestors: [boxoffice, infrastructure, acme-corp] children: [] - descendants: [] --- apiVersion: backstage.io/v1alpha1 kind: User diff --git a/packages/catalog-model/examples/acme/team-d-group.yaml b/packages/catalog-model/examples/acme/team-d-group.yaml index 5a1a53a2e6..e032ab8b9e 100644 --- a/packages/catalog-model/examples/acme/team-d-group.yaml +++ b/packages/catalog-model/examples/acme/team-d-group.yaml @@ -6,9 +6,7 @@ metadata: spec: type: team parent: boxoffice - ancestors: [boxoffice, infrastructure, acme-corp] children: [] - descendants: [] --- apiVersion: backstage.io/v1alpha1 kind: User diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts index 8b7bef8ff7..755a68cede 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts @@ -34,9 +34,7 @@ describe('GroupV1alpha1Validator', () => { spec: { type: 'squad', parent: 'group-a', - ancestors: ['group-a', 'global-synergies', 'acme-corp'], children: ['child-a', 'child-b'], - descendants: ['desc-a', 'desc-b'], }, }; }); @@ -85,26 +83,6 @@ describe('GroupV1alpha1Validator', () => { await expect(validator.check(entity)).rejects.toThrow(/parent/); }); - it('rejects missing ancestors', async () => { - delete (entity as any).spec.ancestors; - await expect(validator.check(entity)).rejects.toThrow(/ancestor/); - }); - - it('rejects empty ancestors', async () => { - (entity as any).spec.ancestors = ['']; - await expect(validator.check(entity)).rejects.toThrow(/ancestor/); - }); - - it('rejects undefined ancestors', async () => { - (entity as any).spec.ancestors = [undefined]; - await expect(validator.check(entity)).rejects.toThrow(/ancestor/); - }); - - it('accepts no ancestors', async () => { - (entity as any).spec.ancestors = []; - await expect(validator.check(entity)).resolves.toBe(true); - }); - it('rejects missing children', async () => { delete (entity as any).spec.children; await expect(validator.check(entity)).rejects.toThrow(/children/); @@ -124,24 +102,4 @@ describe('GroupV1alpha1Validator', () => { (entity as any).spec.children = []; await expect(validator.check(entity)).resolves.toBe(true); }); - - it('rejects missing descendants', async () => { - delete (entity as any).spec.descendants; - await expect(validator.check(entity)).rejects.toThrow(/descendants/); - }); - - it('rejects empty descendants', async () => { - (entity as any).spec.descendants = ['']; - await expect(validator.check(entity)).rejects.toThrow(/descendants/); - }); - - it('rejects undefined descendants', async () => { - (entity as any).spec.descendants = [undefined]; - await expect(validator.check(entity)).rejects.toThrow(/descendants/); - }); - - it('accepts no descendants', async () => { - (entity as any).spec.descendants = []; - await expect(validator.check(entity)).resolves.toBe(true); - }); }); diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index 7be432ad1f..5f73d00e66 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -32,21 +32,11 @@ const schema = yup.object>({ // one element and there is no simple workaround -_- // the cast is there to convince typescript that the array itself is // required without using .required() - ancestors: yup.array(yup.string().required()).test({ - name: 'isDefined', - message: 'ancestors must be defined', - test: v => Boolean(v), - }) as yup.ArraySchema, children: yup.array(yup.string().required()).test({ name: 'isDefined', message: 'children must be defined', test: v => Boolean(v), }) as yup.ArraySchema, - descendants: yup.array(yup.string().required()).test({ - name: 'isDefined', - message: 'descendants must be defined', - test: v => Boolean(v), - }) as yup.ArraySchema, }) .required(), }); @@ -57,23 +47,7 @@ export interface GroupEntityV1alpha1 extends Entity { spec: { type: string; parent?: string; - /** - * @deprecated This field will disappear on Dec 6th, 2020. Please remove - * any consuming code. Producers can stop producing this field - * before that date, as long as the catalog backend uses the - * BuiltinKindsEntityProcessor which inserts the fields in the - * mean time. - */ - ancestors: string[]; children: string[]; - /** - * @deprecated This field will disappear on Dec 6th, 2020. Please remove - * any consuming code. Producers can stop producing this field - * before that date, as long as the catalog backend uses the - * BuiltinKindsEntityProcessor which inserts the fields in the - * mean time. - */ - descendants: string[]; }; } diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index e456dfd7a2..5fa63d800a 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -23,34 +23,6 @@ import { import { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; describe('BuiltinKindsEntityProcessor', () => { - it('fills in fields for #3049', async () => { - const p = new BuiltinKindsEntityProcessor(); - const result = await p.preProcessEntity({ - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'n', - }, - spec: { - type: 't', - children: [], - } as any, - }); - expect(result).toEqual({ - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'n', - }, - spec: { - type: 't', - children: [], - ancestors: [], - descendants: [], - }, - }); - }); - describe('postProcessEntity', () => { const processor = new BuiltinKindsEntityProcessor(); const location = { type: 'a', target: 'b' }; @@ -215,9 +187,7 @@ describe('BuiltinKindsEntityProcessor', () => { spec: { type: 't', parent: 'p', - ancestors: [], children: ['c'], - descendants: [], }, }; diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index 62b496dd65..aa68222a4e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -53,25 +53,6 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { userEntityV1alpha1Validator, ]; - async preProcessEntity(entity: Entity): Promise { - // NOTE(freben): Part of Group field deprecation on Nov 22nd, 2020. Fields - // scheduled for removal Dec 6th, 2020. This code can be deleted after that - // point. See https://github.com/backstage/backstage/issues/3049 - if ( - entity.apiVersion === 'backstage.io/v1alpha1' && - entity.kind === 'Group' && - entity.spec - ) { - if (!entity.spec.ancestors) { - entity.spec.ancestors = []; - } - if (!entity.spec.descendants) { - entity.spec.descendants = []; - } - } - return entity; - } - async validateEntityKind(entity: Entity): Promise { for (const validator of this.validators) { const result = await validator.check(entity); diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts b/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts index e644bab22a..e964a03ce0 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts @@ -47,7 +47,7 @@ function group(data: RecursivePartial): GroupEntity { apiVersion: 'backstage.io/v1alpha1', kind: 'Group', metadata: { name: 'name' }, - spec: { type: 'type', ancestors: [], children: [], descendants: [] }, + spec: { type: 'type', children: [] }, } as GroupEntity, data, ); @@ -173,9 +173,7 @@ describe('readLdapGroups', () => { }, spec: { type: 'type-value', - ancestors: [], children: [], - descendants: [], }, }), ]); diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts b/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts index 63fce00bd8..b7315fd291 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts +++ b/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts @@ -150,9 +150,7 @@ export async function readLdapGroups( }, spec: { type: 'unknown', - ancestors: [], children: [], - descendants: [], }, }; diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts index 3c2f33e922..b0d446c417 100644 --- a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts @@ -49,9 +49,7 @@ function group(data: RecursivePartial): GroupEntity { name: 'name', }, spec: { - ancestors: [], children: [], - descendants: [], type: 'team', }, } as GroupEntity, @@ -306,33 +304,20 @@ describe('read microsoft graph', () => { resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf); expect(rootGroup.spec.parent).toBeUndefined(); - expect(rootGroup.spec.ancestors).toEqual(expect.arrayContaining([])); expect(rootGroup.spec.children).toEqual( expect.arrayContaining(['a', 'b']), ); - expect(rootGroup.spec.descendants).toEqual( - expect.arrayContaining(['a', 'b', 'c']), - ); expect(groupA.spec.parent).toEqual('root'); - expect(groupA.spec.ancestors).toEqual(expect.arrayContaining(['root'])); expect(groupA.spec.children).toEqual(expect.arrayContaining([])); - expect(groupA.spec.descendants).toEqual(expect.arrayContaining([])); expect(groupB.spec.parent).toEqual('root'); - expect(groupB.spec.ancestors).toEqual(expect.arrayContaining(['root'])); expect(groupB.spec.children).toEqual(expect.arrayContaining(['c'])); - expect(groupB.spec.descendants).toEqual(expect.arrayContaining(['c'])); expect(groupC.spec.parent).toEqual('b'); - expect(groupC.spec.ancestors).toEqual( - expect.arrayContaining(['root', 'b']), - ); expect(groupC.spec.children).toEqual(expect.arrayContaining([])); - expect(groupC.spec.descendants).toEqual(expect.arrayContaining([])); expect(user1.spec.memberOf).toEqual(expect.arrayContaining(['a'])); - expect(user2.spec.memberOf).toEqual(expect.arrayContaining(['b', 'c'])); }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts index 6cde2649c4..532e1c3777 100644 --- a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import limiterFactory from 'p-limit'; import { buildMemberOf, buildOrgHierarchy } from '../util/org'; import { MicrosoftGraphClient } from './client'; import { @@ -21,7 +22,6 @@ import { MICROSOFT_GRAPH_TENANT_ID_ANNOTATION, MICROSOFT_GRAPH_USER_ID_ANNOTATION, } from './constants'; -import limiterFactory from 'p-limit'; export function normalizeEntityName(name: string): string { return name @@ -98,7 +98,7 @@ export async function readMicrosoftGraphOrganization( ): Promise<{ rootGroup: GroupEntity; // With all relations empty }> { - // For now we expect a single root orgranization + // For now we expect a single root organization const organization = await client.getOrganization(tenantId); const name = normalizeEntityName(organization.displayName!); const rootGroup: GroupEntity = { @@ -113,9 +113,7 @@ export async function readMicrosoftGraphOrganization( }, spec: { type: 'root', - ancestors: [], children: [], - descendants: [], }, }; @@ -165,9 +163,7 @@ export async function readMicrosoftGraphGroups( spec: { type: 'team', // TODO: We could include a group email and picture - ancestors: [], children: [], - descendants: [], }, }; @@ -278,7 +274,7 @@ export function resolveRelations( }); }); - // Make sure that all groups have proper ancestors and descendants + // Make sure that all groups have proper parents and children buildOrgHierarchy(groups); // Set relations for all users diff --git a/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts b/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts index 54afdd1bdc..1e35f44712 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts @@ -100,9 +100,7 @@ describe('github', () => { spec: { type: 'team', parent: 'parent', - ancestors: [], children: [], - descendants: [], }, }), ], diff --git a/plugins/catalog-backend/src/ingestion/processors/util/github.ts b/plugins/catalog-backend/src/ingestion/processors/util/github.ts index 96cf4a7668..8be96c41de 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/github.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/github.ts @@ -162,9 +162,7 @@ export async function getOrganizationTeams( }, spec: { type: 'team', - ancestors: [], children: [], - descendants: [], }, }; diff --git a/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts b/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts index f7afd63101..c9056eebb4 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts @@ -26,7 +26,7 @@ function g( apiVersion: 'backstage.io/v1alpha1', kind: 'Group', metadata: { name }, - spec: { type: 'team', parent, children, ancestors: [], descendants: [] }, + spec: { type: 'team', parent, children }, }; } @@ -43,28 +43,16 @@ describe('buildOrgHierarchy', () => { expect(d.spec.children).toEqual([]); }); - it('fills out descendants', () => { - const a = g('a', undefined, []); - const b = g('b', 'a', []); - const c = g('c', 'b', []); - const d = g('d', 'a', []); + it('sets parent of groups children', () => { + const a = g('a', undefined, ['b', 'd']); + const b = g('b', undefined, ['c']); + const c = g('c', undefined, []); + const d = g('d', undefined, []); buildOrgHierarchy([a, b, c, d]); - expect(a.spec.descendants).toEqual(expect.arrayContaining(['b', 'c', 'd'])); - expect(b.spec.descendants).toEqual(expect.arrayContaining(['c'])); - expect(c.spec.descendants).toEqual([]); - expect(d.spec.descendants).toEqual([]); - }); - - it('fills out ancestors', () => { - const a = g('a', undefined, []); - const b = g('b', 'a', []); - const c = g('c', 'b', []); - const d = g('d', 'a', []); - buildOrgHierarchy([a, b, c, d]); - expect(a.spec.ancestors).toEqual([]); - expect(b.spec.ancestors).toEqual(expect.arrayContaining(['a'])); - expect(c.spec.ancestors).toEqual(expect.arrayContaining(['a', 'b'])); - expect(d.spec.ancestors).toEqual(expect.arrayContaining(['a'])); + expect(a.spec.parent).toBeUndefined(); + expect(b.spec.parent).toBe('a'); + expect(c.spec.parent).toBe('b'); + expect(d.spec.parent).toBe('a'); }); }); @@ -76,7 +64,7 @@ describe('buildMemberOf', () => { const u: UserEntity = { apiVersion: 'backstage.io/v1alpha1', kind: 'User', - metadata: { name }, + metadata: { name: 'n' }, spec: { profile: {}, memberOf: ['c'] }, }; diff --git a/plugins/catalog-backend/src/ingestion/processors/util/org.ts b/plugins/catalog-backend/src/ingestion/processors/util/org.ts index b033fe99d7..787e408e60 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/org.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/org.ts @@ -35,62 +35,17 @@ export function buildOrgHierarchy(groups: GroupEntity[]) { } // - // Make sure that g.descendants is complete + // Make sure that g.children.parent is g // - function visitDescendants(current: GroupEntity): string[] { - if (current.spec.descendants.length) { - return current.spec.descendants; - } - - const accumulator = new Set(); - for (const childName of current.spec.children) { - accumulator.add(childName); + for (const group of groups) { + const selfName = group.metadata.name; + for (const childName of group.spec.children) { const child = groupsByName.get(childName); - if (child) { - for (const d of visitDescendants(child)) { - accumulator.add(d); - } + if (child && !child.spec.parent) { + child.spec.parent = selfName; } } - - const descendants = Array.from(accumulator); - current.spec.descendants = descendants; - return descendants; - } - - for (const group of groups) { - visitDescendants(group); - } - - // - // Make sure that g.ancestors is complete - // - - function visitAncestors(current: GroupEntity): string[] { - if (current.spec.ancestors.length) { - return current.spec.ancestors; - } - - let ancestors: string[]; - const parentName = current.spec.parent; - if (!parentName) { - ancestors = []; - } else { - const parent = groupsByName.get(parentName); - if (parent) { - ancestors = [parentName, ...visitAncestors(parent)]; - } else { - ancestors = [parentName]; - } - } - - current.spec.ancestors = ancestors; - return ancestors; - } - - for (const group of groups) { - visitAncestors(group); } } @@ -100,15 +55,24 @@ export function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) { const groupsByName = new Map(groups.map(g => [g.metadata.name, g])); users.forEach(user => { - const transitiveMemberOf = new Set([...user.spec.memberOf]); + const transitiveMemberOf = new Set(); - user.spec.memberOf.forEach(groupName => { - const group = groupsByName.get(groupName); - - if (group) { - group.spec.ancestors.forEach(g => transitiveMemberOf.add(g)); + const todo = [...user.spec.memberOf]; + for (;;) { + const current = todo.pop(); + if (!current) { + break; } - }); + + if (!transitiveMemberOf.has(current)) { + transitiveMemberOf.add(current); + const group = groupsByName.get(current); + if (group?.spec.parent) { + todo.push(group.spec.parent); + } + } + } + user.spec.memberOf = [...transitiveMemberOf]; }); } From 44a81f7ceac58344df00095d0b3e388b6f4b8229 Mon Sep 17 00:00:00 2001 From: Remi Date: Mon, 30 Nov 2020 23:37:13 +0100 Subject: [PATCH 02/19] feat(storybook): add drawer --- .../src/components/Drawer/Drawer.stories.tsx | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 packages/core/src/components/Drawer/Drawer.stories.tsx diff --git a/packages/core/src/components/Drawer/Drawer.stories.tsx b/packages/core/src/components/Drawer/Drawer.stories.tsx new file mode 100644 index 0000000000..9bb60c78a4 --- /dev/null +++ b/packages/core/src/components/Drawer/Drawer.stories.tsx @@ -0,0 +1,165 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState } from 'react'; +import { + Drawer, + Button, + Typography, + makeStyles, + IconButton, +} from '@material-ui/core'; +import Close from '@material-ui/icons/Close'; + +export default { + title: 'Layout/Drawer', + component: Drawer, +}; + +const useDrawerStyles = makeStyles({ + paper: { + width: '50%', + justifyContent: 'space-between', + padding: '20px', + }, +}); + +const useDrawerContentStyles = makeStyles({ + header: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + }, + icon: { + fontSize: 20, + }, + content: { + height: '80%', + backgroundColor: '#EEEEEE', + }, + secondaryAction: { + marginLeft: '20px', + }, +}); + +/* Example content wrapped inside the Drawer component */ +const DrawerContent = ({ + toggleDrawer, +}: { + toggleDrawer: (isOpen: boolean) => void; +}) => { + const classes = useDrawerContentStyles(); + + return ( + <> +
+ Side Panel Title + toggleDrawer(false)} + color="inherit" + > + + +
+
+
+ + +
+ + ); +}; + +/* Default drawer can toggle open or closed. + * It can be cancelled by clicking the overlay + * or pressing the esc key. + */ +export const DefaultDrawer = () => { + const [isOpen, toggleDrawer] = useState(false); + const classes = useDrawerStyles(); + + return ( + <> + + toggleDrawer(false)} + > + + + + ); +}; + +/* Persistent drawer works like the default one - + * except that the content sits on the same level + * as the main content and you can't cancel it by + * clicking the overlay or pressing the esc key. + * + * Set the Drawer variant props: 'persistent' + */ +export const PersistentDrawer = () => { + const [isOpen, toggleDrawer] = useState(false); + const classes = useDrawerStyles(); + + return ( + <> + + toggleDrawer(false)} + > + + + + ); +}; From 8d28d2c98448f3a77add6997c31fa2535b932dc3 Mon Sep 17 00:00:00 2001 From: Remi Date: Fri, 4 Dec 2020 16:35:18 +0100 Subject: [PATCH 03/19] feat(storybook-drawer): use theme spacing --- .../src/components/Drawer/Drawer.stories.tsx | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/packages/core/src/components/Drawer/Drawer.stories.tsx b/packages/core/src/components/Drawer/Drawer.stories.tsx index 9bb60c78a4..b399cfed8e 100644 --- a/packages/core/src/components/Drawer/Drawer.stories.tsx +++ b/packages/core/src/components/Drawer/Drawer.stories.tsx @@ -21,6 +21,8 @@ import { Typography, makeStyles, IconButton, + createStyles, + Theme, } from '@material-ui/core'; import Close from '@material-ui/icons/Close'; @@ -29,31 +31,35 @@ export default { component: Drawer, }; -const useDrawerStyles = makeStyles({ - paper: { - width: '50%', - justifyContent: 'space-between', - padding: '20px', - }, -}); +const useDrawerStyles = makeStyles((theme: Theme) => + createStyles({ + paper: { + width: '50%', + justifyContent: 'space-between', + padding: theme.spacing(2.5), + }, + }), +); -const useDrawerContentStyles = makeStyles({ - header: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - }, - icon: { - fontSize: 20, - }, - content: { - height: '80%', - backgroundColor: '#EEEEEE', - }, - secondaryAction: { - marginLeft: '20px', - }, -}); +const useDrawerContentStyles = makeStyles((theme: Theme) => + createStyles({ + header: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + }, + icon: { + fontSize: 20, + }, + content: { + height: '80%', + backgroundColor: '#EEEEEE', + }, + secondaryAction: { + marginLeft: theme.spacing(2.5), + }, + }), +); /* Example content wrapped inside the Drawer component */ const DrawerContent = ({ From e3071a0d496b60498c7caa9ab09fddc786f3a1b2 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 4 Dec 2020 09:12:29 -0700 Subject: [PATCH 04/19] Add support for multiple types of entity cost breakdown --- .../cost-insights-quick-lizards-smash.md | 7 + plugins/cost-insights/package.json | 2 + .../cost-insights/src/api/CostInsightsApi.ts | 1 + .../ProductInsights/ProductInsights.test.tsx | 12 +- .../ProductEntityDialog.test.tsx | 134 +++ .../ProductEntityDialog.tsx | 178 +--- .../ProductEntityTable.tsx | 174 ++++ .../ProductInsightsCard.test.tsx | 14 +- .../ProductInsightsCard.tsx | 22 +- .../ProductInsightsChart.tsx | 45 +- .../ProjectGrowthAlertCard.tsx | 4 +- .../ProjectGrowthInstructionsPage.tsx | 42 +- .../UnlabeledDataflowAlertCard.tsx | 6 +- plugins/cost-insights/src/types/Entity.ts | 112 ++- plugins/cost-insights/src/utils/assert.ts | 6 + plugins/cost-insights/src/utils/formatters.ts | 4 +- plugins/cost-insights/src/utils/grammar.ts | 14 - plugins/cost-insights/src/utils/mockData.ts | 773 ++++++++++-------- yarn.lock | 10 + 19 files changed, 933 insertions(+), 627 deletions(-) create mode 100644 .changeset/cost-insights-quick-lizards-smash.md create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.test.tsx create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityTable.tsx diff --git a/.changeset/cost-insights-quick-lizards-smash.md b/.changeset/cost-insights-quick-lizards-smash.md new file mode 100644 index 0000000000..eae60fc744 --- /dev/null +++ b/.changeset/cost-insights-quick-lizards-smash.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-cost-insights': minor +--- + +Add support for multiple types of entity cost breakdown. + +This change is backwards-incompatible with plugin-cost-insights 0.3.x; the `entities` field on Entity returned in product cost queries changed from `Entity[]` to `Record; + /** * Get current cost alerts for a given group. These show up as Action Items for the group on the * Cost Insights page. Alerts may include cost-saving recommendations, such as infrastructure diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx index 85f39f9388..45d0946f86 100644 --- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx @@ -40,7 +40,7 @@ const MockComputeEngine: Product = { const MockComputeEngineInsights: Entity = { id: 'compute-engine', - entities: [], + entities: {}, aggregation: [0, 0], change: { ratio: 0, @@ -55,7 +55,7 @@ const MockCloudDataflow: Product = { const MockCloudDataflowInsights: Entity = { id: MockCloudDataflow.kind, - entities: [], + entities: {}, aggregation: [1_000, 2_000], change: { ratio: 1, @@ -70,7 +70,7 @@ const MockCloudStorage: Product = { const MockCloudStorageInsights: Entity = { id: MockCloudStorage.kind, - entities: [], + entities: {}, aggregation: [2_000, 4_000], change: { ratio: 1, @@ -85,7 +85,7 @@ const MockBigQuery: Product = { const MockBigQueryInsights: Entity = { id: MockBigQuery.kind, - entities: [], + entities: {}, aggregation: [8_000, 16_000], change: { ratio: 1, @@ -100,7 +100,7 @@ const MockBigTable: Product = { const MockBigTableInsights: Entity = { id: MockBigTable.kind, - entities: [], + entities: {}, aggregation: [16_000, 32_000], change: { ratio: 1, @@ -115,7 +115,7 @@ const MockCloudPubSub: Product = { const MockCloudPubSubInsights: Entity = { id: MockCloudPubSub.kind, - entities: [], + entities: {}, aggregation: [32_000, 64_000], change: { ratio: 1, diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.test.tsx new file mode 100644 index 0000000000..076edf60a3 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.test.tsx @@ -0,0 +1,134 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { ProductEntityDialog } from './ProductEntityDialog'; +import { render } from '@testing-library/react'; +import { Entity } from '../../types'; + +const atomicEntity: Entity = { + id: null, + aggregation: [0, 0], + change: { ratio: 0, amount: 0 }, + entities: {}, +}; + +const singleBreakdownEntity = { + ...atomicEntity, + entities: { + SKU: [ + { + id: 'sku-1', + aggregation: [0, 0], + change: { ratio: 0, amount: 0 }, + entities: {}, + }, + { + id: 'sku-2', + aggregation: [0, 0], + change: { ratio: 0, amount: 0 }, + entities: {}, + }, + ] as Entity[], + }, +}; + +const multiBreakdownEntity = { + ...singleBreakdownEntity, + entities: { + ...singleBreakdownEntity.entities, + deployment: [ + { + id: 'd-1', + aggregation: [0, 0], + change: { ratio: 0, amount: 0 }, + entities: {}, + }, + { + id: 'd-2', + aggregation: [0, 0], + change: { ratio: 0, amount: 0 }, + entities: {}, + }, + ] as Entity[], + }, +}; + +describe('', () => { + it('Should error if no sub-entities exist', () => { + expect(() => + render( + wrapInTestApp( + , + ), + ), + ).toThrow(); + }); + + it('Should not show tabs for a single sub-entity type', () => { + const { queryByText } = render( + wrapInTestApp( + , + ), + ); + expect(queryByText('Breakdown by SKU')).not.toBeInTheDocument(); + }); + + it('Should show tabs when multiple sub-entity types exist', () => { + const { getByText } = render( + wrapInTestApp( + , + ), + ); + expect(getByText('Breakdown by SKU')).toBeInTheDocument(); + expect(getByText('Breakdown by deployment')).toBeInTheDocument(); + expect(getByText('sku-1')).toBeInTheDocument(); + }); + + it('Shows the pre-selected tab, if provided', () => { + const { getByText } = render( + wrapInTestApp( + , + ), + ); + expect(getByText('d-1')).toBeInTheDocument(); + }); +}); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx index d617c0df35..a98e5dc87a 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx @@ -14,179 +14,55 @@ * limitations under the License. */ -import React from 'react'; -import classnames from 'classnames'; -import { Table, TableColumn } from '@backstage/core'; -import { Dialog, IconButton, Typography } from '@material-ui/core'; +import React, { useState } from 'react'; +import { HeaderTabs } from '@backstage/core'; +import { Dialog, IconButton } from '@material-ui/core'; import { default as CloseButton } from '@material-ui/icons/Close'; -import { CostGrowthIndicator } from '../CostGrowth'; -import { costFormatter, formatPercent } from '../../utils/formatters'; import { useEntityDialogStyles as useStyles } from '../../utils/styles'; -import { BarChartOptions, Entity } from '../../types'; - -function createRenderer(col: keyof RowData, classes: Record) { - return function render(rowData: {}): JSX.Element { - const row = rowData as RowData; - const rowStyles = classnames(classes.row, { - [classes.rowTotal]: row.id === 'total', - [classes.colFirst]: col === 'label', - [classes.colLast]: col === 'ratio', - }); - - switch (col) { - case 'previous': - case 'current': - return ( - - {costFormatter.format(row[col])} - - ); - case 'ratio': - return ( - formatPercent(Math.abs(amount))} - /> - ); - default: - return {row.label}; - } - }; -} - -// material-table does not support fixed rows. Override the sorting algorithm -// to force Total row to bottom by default or when a user sort toggles a column. -function createSorter(field?: keyof Omit) { - return function rowSort(data1: {}, data2: {}): number { - const a = data1 as RowData; - const b = data2 as RowData; - if (a.id === 'total') return 1; - if (b.id === 'total') return 1; - if (field === 'label') return a.label.localeCompare(b.label); - - return field - ? a[field] - b[field] - : b.previous + b.current - (a.previous - a.current); - }; -} - -const defaultEntity: Entity = { - id: null, - aggregation: [0, 0], - change: { ratio: 0, amount: 0 }, - entities: [], -}; - -type RowData = { - id: string; - label: string; - previous: number; - current: number; - ratio: number; -}; - -type ProductEntityDialogOptions = Partial< - Pick ->; +import { Entity } from '../../types'; +import { + ProductEntityTable, + ProductEntityTableOptions, +} from './ProductEntityTable'; +import { findAlways } from '../../utils/assert'; type ProductEntityDialogProps = { open: boolean; - entity?: Entity; - entitiesLabel: string; - options?: ProductEntityDialogOptions; + entity: Entity; + options?: ProductEntityTableOptions; onClose: () => void; }; export const ProductEntityDialog = ({ open, - entity = defaultEntity, - entitiesLabel, + entity, options = {}, onClose, }: ProductEntityDialogProps) => { const classes = useStyles(); - - const data = Object.assign( - { - previousName: 'Previous', - currentName: 'Current', - }, - options, + const labels = Object.keys(entity.entities); + const [selectedLabel, setSelectedLabel] = useState( + findAlways(labels, _ => true), ); - const firstColClasses = classnames(classes.column, classes.colFirst); - const lastColClasses = classnames(classes.column, classes.colLast); - - const columns: TableColumn[] = [ - { - field: 'label', - title: ( - {entitiesLabel} - ), - render: createRenderer('label', classes), - customSort: createSorter('label'), - width: '33.33%', - }, - { - field: 'previous', - title: ( - {data.previousName} - ), - align: 'right', - render: createRenderer('previous', classes), - customSort: createSorter('previous'), - }, - { - field: 'current', - title: ( - {data.currentName} - ), - align: 'right', - render: createRenderer('current', classes), - customSort: createSorter('current'), - }, - { - field: 'ratio', - title: M/M, - align: 'right', - render: createRenderer('ratio', classes), - customSort: createSorter('ratio'), - }, - ]; - - const rowData: RowData[] = entity.entities - .map(e => ({ - id: e.id || 'Unknown', - label: e.id || 'Unknown', - previous: e.aggregation[0], - current: e.aggregation[1], - ratio: e.change.ratio, - })) - .concat({ - id: 'total', - label: 'Total', - previous: entity.aggregation[0], - current: entity.aggregation[1], - ratio: entity.change.ratio, - }) - .sort(createSorter()); + const tabs = labels.map((label, index) => ({ + id: index.toString(), + label: `Breakdown by ${label}`, + })); return ( - setSelectedLabel(labels[index])} + /> + ); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityTable.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityTable.tsx new file mode 100644 index 0000000000..7c239ba30d --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityTable.tsx @@ -0,0 +1,174 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import classnames from 'classnames'; +import { Table, TableColumn } from '@backstage/core'; +import { Typography } from '@material-ui/core'; +import { costFormatter, formatPercent } from '../../utils/formatters'; +import { useEntityDialogStyles as useStyles } from '../../utils/styles'; +import { CostGrowthIndicator } from '../CostGrowth'; +import { BarChartOptions, Entity } from '../../types'; + +export type ProductEntityTableOptions = Partial< + Pick +>; + +type RowData = { + id: string; + label: string; + previous: number; + current: number; + ratio: number; +}; + +function createRenderer(col: keyof RowData, classes: Record) { + return function render(rowData: {}): JSX.Element { + const row = rowData as RowData; + const rowStyles = classnames(classes.row, { + [classes.rowTotal]: row.id === 'total', + [classes.colFirst]: col === 'label', + [classes.colLast]: col === 'ratio', + }); + + switch (col) { + case 'previous': + case 'current': + return ( + + {costFormatter.format(row[col])} + + ); + case 'ratio': + return ( + formatPercent(Math.abs(amount))} + /> + ); + default: + return {row.label}; + } + }; +} + +// material-table does not support fixed rows. Override the sorting algorithm +// to force Total row to bottom by default or when a user sort toggles a column. +function createSorter(field?: keyof Omit) { + return function rowSort(data1: {}, data2: {}): number { + const a = data1 as RowData; + const b = data2 as RowData; + if (a.id === 'total') return 1; + if (b.id === 'total') return 1; + if (field === 'label') return a.label.localeCompare(b.label); + + return field + ? a[field] - b[field] + : b.previous + b.current - (a.previous - a.current); + }; +} + +type ProductEntityTableProps = { + entityLabel: string; + entity: Entity; + options: ProductEntityTableOptions; +}; + +export const ProductEntityTable = ({ + entityLabel, + entity, + options, +}: ProductEntityTableProps) => { + const classes = useStyles(); + const entities = entity.entities[entityLabel]; + + const data = Object.assign( + { + previousName: 'Previous', + currentName: 'Current', + }, + options, + ); + + const firstColClasses = classnames(classes.column, classes.colFirst); + const lastColClasses = classnames(classes.column, classes.colLast); + + const columns: TableColumn[] = [ + { + field: 'label', + title: {entityLabel}, + render: createRenderer('label', classes), + customSort: createSorter('label'), + width: '33.33%', + }, + { + field: 'previous', + title: ( + {data.previousName} + ), + align: 'right', + render: createRenderer('previous', classes), + customSort: createSorter('previous'), + }, + { + field: 'current', + title: ( + {data.currentName} + ), + align: 'right', + render: createRenderer('current', classes), + customSort: createSorter('current'), + }, + { + field: 'ratio', + title: M/M, + align: 'right', + render: createRenderer('ratio', classes), + customSort: createSorter('ratio'), + }, + ]; + + const rowData: RowData[] = entities + .map(e => ({ + id: e.id || 'Unknown', + label: e.id || 'Unknown', + previous: e.aggregation[0], + current: e.aggregation[1], + ratio: e.change.ratio, + })) + .concat({ + id: 'total', + label: 'Total', + previous: entity.aggregation[0], + current: entity.aggregation[1], + ratio: entity.change.ratio, + }) + .sort(createSorter()); + + return ( +
+ ); +}; diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx index 385ff82f9b..5600ed832f 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx @@ -42,7 +42,7 @@ const costInsightsApi = (entity: Entity): Partial => ({ const mockProductCost = createMockEntity(() => ({ id: 'test-id', - entities: [], + entities: {}, aggregation: [3000, 4000], change: { ratio: 0.23, @@ -91,21 +91,21 @@ describe('', () => { it('Should render the right subheader for products with cost data', async () => { const entity = { ...mockProductCost, - entities: [...Array(1000)].map(createMockEntity), + entities: { entity: [...Array(1000)].map(createMockEntity) }, }; const rendered = await renderProductInsightsCardInTestApp( entity, MockComputeEngine, ); - const subheader = 'entities, sorted by cost'; - const subheaderRgx = new RegExp(`${entity.entities.length} ${subheader}`); - expect(rendered.getByText(subheaderRgx)).toBeInTheDocument(); + expect( + rendered.getByText(/1000 entities, sorted by cost/), + ).toBeInTheDocument(); }); it('Should render the right subheader if there is no cost data or change data', async () => { const entity: Entity = { id: 'test-id', - entities: [], + entities: {}, aggregation: [0, 0], change: { ratio: 0, amount: 0 }, }; @@ -135,7 +135,7 @@ describe('', () => { it(`Should display the correct relative time for ${duration}`, async () => { const entity = { ...mockProductCost, - entities: [...Array(3)].map(createMockEntity), + entities: { entity: [...Array(3)].map(createMockEntity) }, }; const rendered = await renderProductInsightsCardInTestApp( entity, diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx index e633d6653d..e4cb475ab5 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx @@ -21,6 +21,7 @@ import React, { useRef, useState, } from 'react'; +import pluralize from 'pluralize'; import { InfoCard } from '@backstage/core'; import { Typography } from '@material-ui/core'; import { default as Alert } from '@material-ui/lab/Alert'; @@ -30,12 +31,12 @@ import { useProductInsightsCardStyles as useStyles } from '../../utils/styles'; import { DefaultLoadingAction } from '../../utils/loading'; import { Duration, Entity, Maybe, Product } from '../../types'; import { - useLastCompleteBillingDate, - useScroll, - useLoading, MapLoadingToProps, + useLastCompleteBillingDate, + useLoading, + useScroll, } from '../../hooks'; -import { pluralOf } from '../../utils/grammar'; +import { findFirstKey } from '../../utils/assert'; type LoadingProps = (isLoading: boolean) => void; @@ -91,13 +92,12 @@ export const ProductInsightsCard = ({ } }, [product, duration, onSelectAsync, dispatchLoadingProduct]); - const entities = entity?.entities ?? []; - const subheader = entities.length - ? `${entities.length} ${pluralOf( - entities.length, - 'entity', - 'entities', - )}, sorted by cost` + // Only a single entities Record for the root product entity is supported + const entityKey = findFirstKey(entity?.entities); + const entities = entityKey ? entity!.entities[entityKey] : []; + + const subheader = entityKey + ? `${pluralize(entityKey, entities.length, true)}, sorted by cost` : null; const headerProps = { classes: classes, diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx index 2c26f62574..d7c796adf3 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx @@ -20,6 +20,7 @@ import { TooltipProps as RechartsTooltipProps, RechartsFunction, } from 'recharts'; +import pluralize from 'pluralize'; import { Box, Typography } from '@material-ui/core'; import { default as FullScreenIcon } from '@material-ui/icons/Fullscreen'; import { LegendItem } from '../LegendItem'; @@ -32,8 +33,13 @@ import { BarChartTooltipItem, BarChartLegendOptions, } from '../BarChart'; -import { pluralOf } from '../../utils/grammar'; -import { findAlways, notEmpty, isUndefined } from '../../utils/assert'; +import { + findAlways, + notEmpty, + isUndefined, + findFirstKey, + assertAlways, +} from '../../utils/assert'; import { formatPeriod, formatPercent } from '../../utils/formatters'; import { titleOf, @@ -62,19 +68,26 @@ export const ProductInsightsChart = ({ }: ProductInsightsChartProps) => { const classes = useStyles(); const layoutClasses = useLayoutStyles(); + + // Only a single entities Record for the root product entity is supported + const entityLabel = assertAlways(findFirstKey(entity.entities)); + const entities = entity.entities[entityLabel] ?? []; + const [activeLabel, setActive] = useState>(); const [selectLabel, setSelected] = useState>(); const isSelected = useMemo(() => !isUndefined(selectLabel), [selectLabel]); + const isClickable = useMemo(() => { - const breakdownEntities = - entity.entities.find(e => e.id === activeLabel)?.entities ?? []; - return breakdownEntities.length > 0; - }, [entity, activeLabel]); + const breakdowns = Object.keys( + entities.find(e => e.id === activeLabel)?.entities ?? {}, + ); + return breakdowns.length > 0; + }, [entities, activeLabel]); const legendTitle = `Cost ${entity.change.ratio <= 0 ? 'Savings' : 'Growth'}`; const costStart = entity.aggregation[0]; const costEnd = entity.aggregation[1]; - const resources = entity.entities.map(resourceOf); + const resources = entities.map(resourceOf); const options: Partial = { previousName: formatPeriod(duration, billingDate, false), @@ -120,15 +133,14 @@ export const ProductInsightsChart = ({ const title = titleOf(label); const items = payload.map(tooltipItemOf).filter(notEmpty); - const activeEntity = findAlways(entity.entities, e => e.id === id); + const activeEntity = findAlways(entities, e => e.id === id); const ratio = activeEntity.change.ratio; - const breakdownEntities = activeEntity.entities; - const subtitle = `${breakdownEntities.length} ${pluralOf( - breakdownEntities.length, - entity.entitiesLabel || 'SKU', - )}`; + const breakdowns = Object.keys(activeEntity.entities); - if (breakdownEntities.length) { + if (breakdowns.length) { + const subtitle = breakdowns + .map(b => pluralize(b, activeEntity.entities[b].length, true)) + .join(', '); return ( - {isSelected && entity.entities.length && ( + {isSelected && entities.length && ( setSelected(undefined)} - entity={entity.entities.find(e => e.id === selectLabel)} + entity={findAlways(entities, e => e.id === selectLabel)} options={options} - entitiesLabel={entity.entitiesLabel || 'SKU'} /> )} diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx index bcda21e0af..a3df76088c 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx @@ -15,10 +15,10 @@ */ import React from 'react'; +import pluralize from 'pluralize'; import { InfoCard } from '@backstage/core'; import { ProjectGrowthAlertChart } from './ProjectGrowthAlertChart'; import { ProjectGrowthData } from '../../types'; -import { pluralOf } from '../../utils/grammar'; type ProjectGrowthAlertProps = { alert: ProjectGrowthData; @@ -26,7 +26,7 @@ type ProjectGrowthAlertProps = { export const ProjectGrowthAlertCard = ({ alert }: ProjectGrowthAlertProps) => { const subheader = ` - ${alert.products.length} ${pluralOf(alert.products.length, 'product')}${ + ${pluralize('product', alert.products.length, true)}${ alert.products.length > 1 ? ', sorted by cost' : '' }`; diff --git a/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx b/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx index 41b4ca90f6..4fcc34c87f 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx @@ -72,26 +72,28 @@ export const ProjectGrowthInstructionsPage = () => { ratio: 3, amount: 40_000, }, - entities: [ - { - id: 'service-one', - aggregation: [18_200, 58_500], - entities: [], - change: { ratio: 2.21, amount: 40_300 }, - }, - { - id: 'service-two', - aggregation: [1200, 1300], - entities: [], - change: { ratio: 0.083, amount: 100 }, - }, - { - id: 'service-three', - aggregation: [600, 200], - entities: [], - change: { ratio: -0.666, amount: -400 }, - }, - ], + entities: { + service: [ + { + id: 'service-one', + aggregation: [18_200, 58_500], + entities: {}, + change: { ratio: 2.21, amount: 40_300 }, + }, + { + id: 'service-two', + aggregation: [1200, 1300], + entities: {}, + change: { ratio: 0.083, amount: 100 }, + }, + { + id: 'service-three', + aggregation: [600, 200], + entities: {}, + change: { ratio: -0.666, amount: -400 }, + }, + ], + }, }; return ( diff --git a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.tsx b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.tsx index e56b3b044e..dc020d6ce1 100644 --- a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.tsx +++ b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.tsx @@ -15,11 +15,11 @@ */ import React from 'react'; +import pluralize from 'pluralize'; import { InfoCard } from '@backstage/core'; import { Box } from '@material-ui/core'; import { BarChart, BarChartLegend } from '../BarChart'; import { UnlabeledDataflowData, ResourceData } from '../../types'; -import { pluralOf } from '../../utils/grammar'; import { useBarChartLayoutStyles as useStyles } from '../../utils/styles'; type UnlabeledDataflowAlertProps = { @@ -30,9 +30,9 @@ export const UnlabeledDataflowAlertCard = ({ alert, }: UnlabeledDataflowAlertProps) => { const classes = useStyles(); - const projects = pluralOf(alert.projects.length, 'project'); + const projects = pluralize('project', alert.projects.length, true); const subheader = ` - Showing costs from ${alert.projects.length} ${projects} with unlabeled Dataflow jobs in the last 30 days. + Showing costs from ${projects} with unlabeled Dataflow jobs in the last 30 days. `; const options = { previousName: 'Unlabeled Cost', diff --git a/plugins/cost-insights/src/types/Entity.ts b/plugins/cost-insights/src/types/Entity.ts index 52f6271bb4..e3e1618b4a 100644 --- a/plugins/cost-insights/src/types/Entity.ts +++ b/plugins/cost-insights/src/types/Entity.ts @@ -20,9 +20,8 @@ import { Maybe } from './Maybe'; export interface Entity { id: Maybe; aggregation: [number, number]; - entities: Entity[]; + entities: Record; change: ChangeStatistic; - entitiesLabel?: string; } /* @@ -31,8 +30,15 @@ export interface Entity { An entity could be atomic or composite. An atomic entity is indivisible and cannot be broken into sub-entities. - A composite entity can be broken down recursively into sub-entities - that generate cost **over the same time period**. All costs must sum to the root cost. + A composite entity is divided into sub-entities that account for portions + of the total cost **over the same time period**. The root entity is + expected to only have _one_ Record consisting of the sub-entities to display + in the product panel (keyed by the entity type, such as "service" for + compute entities. + + The root sub-entities may have multiple breakdowns - for example, a + breakdown of an entity cost by SKU vs deployment environment. The sum + aggregated cost of each keyed breakdown should equal the sub-entity's cost. Entities with null ids are considered "unlabeled" - costs without attribution. If an entity is a composite, it may only have one (1) null child but may have any number of @@ -45,44 +51,68 @@ export interface Entity { ratio: 2000, amount: 200 }, - entities: [ - { - id: 'service-a', - aggregation: [0, 100], - change: { - ratio: 100, - amount: 100 - }, - entities: [] - }, - { - id: 'service-b', - aggregation: [0, 100], - change: { - ratio: 100, - amount: 100 - }, - entities: [ - { - id: 'service-b-sku-a', - aggregation: [0, 25], - change: { - ratio: 25, - amount: 25 - }, - entities: [] + entities: { + service: [ + { + id: 'service-a', + aggregation: [0, 100], + change: { + ratio: 100, + amount: 100 }, - { - id: null, // Unlabeled cost for service-b - aggregation: [0, 75], - change: { - ratio: 75, - amount: 75 - }, - entities: [] + entities: {} + }, + { + id: 'service-b', + aggregation: [0, 100], + change: { + ratio: 100, + amount: 100 }, - ] - }, - ] + entities: { + SKU: [ + { + id: 'service-b-sku-a', + aggregation: [0, 25], + change: { + ratio: 25, + amount: 25 + }, + entities: {} + }, + { + id: null, // Unlabeled cost for service-b + aggregation: [0, 75], + change: { + ratio: 75, + amount: 75 + }, + entities: {} + }, + ], + deployment: [ + { + id: 'service-b-env-a', + aggregation: [0, 50], + change: { + ratio: 50, + amount: 50 + }, + entities: {} + }, + { + id: 'service-b-env-b', + aggregation: [0, 50], + change: { + ratio: 50, + amount: 50 + }, + entities: {} + }, + ] + } + }, + ] + } } */ diff --git a/plugins/cost-insights/src/utils/assert.ts b/plugins/cost-insights/src/utils/assert.ts index 19ddabe574..39b710ac3d 100644 --- a/plugins/cost-insights/src/utils/assert.ts +++ b/plugins/cost-insights/src/utils/assert.ts @@ -50,3 +50,9 @@ export function findAlways( ): T { return assertAlways(collection.find(callback)); } + +export function findFirstKey( + record: Record | undefined, +): string | undefined { + return Object.keys(record ?? {}).find(_ => true); +} diff --git a/plugins/cost-insights/src/utils/formatters.ts b/plugins/cost-insights/src/utils/formatters.ts index 9c3b2d643e..01a19c17fa 100644 --- a/plugins/cost-insights/src/utils/formatters.ts +++ b/plugins/cost-insights/src/utils/formatters.ts @@ -15,9 +15,9 @@ */ import moment from 'moment'; +import pluralize from 'pluralize'; import { Duration, DEFAULT_DATE_FORMAT } from '../types'; import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration'; -import { pluralOf } from '../utils/grammar'; export type Period = { periodStart: string; @@ -75,7 +75,7 @@ export function formatCurrency(amount: number, currency?: string): string { const n = Math.round(amount); const numString = numberFormatter.format(n); - return currency ? `${numString} ${pluralOf(n, currency)}` : numString; + return currency ? `${numString} ${pluralize(currency, n)}` : numString; } export function formatPercent(n: number): string { diff --git a/plugins/cost-insights/src/utils/grammar.ts b/plugins/cost-insights/src/utils/grammar.ts index 653c567d0a..b82520dd53 100644 --- a/plugins/cost-insights/src/utils/grammar.ts +++ b/plugins/cost-insights/src/utils/grammar.ts @@ -22,20 +22,6 @@ const vowels = { u: 'U', }; -export const pluralOf = ( - n: number, - string: string, - plural?: string, -): string => { - if (n !== 1) { - if (plural) { - return plural; - } - return string.concat('s'); - } - return string; -}; - export const indefiniteArticleOf = ( articles: [string, string], word: string, diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 76bdf5f08f..2961f67cd9 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -52,7 +52,7 @@ export const createMockEntity = ( const defaultEntity: Entity = { id: 'test-entity', aggregation: [100, 200], - entities: [], + entities: {}, change: { ratio: 0, amount: 0, @@ -517,35 +517,37 @@ export const SampleBigQueryInsights: Entity = { ratio: 3, amount: 20_000, }, - entities: [ - { - id: 'entity-a', - aggregation: [5_000, 10_000], - change: { - ratio: 1, - amount: 5_000, + entities: { + dataset: [ + { + id: 'entity-a', + aggregation: [5_000, 10_000], + change: { + ratio: 1, + amount: 5_000, + }, + entities: {}, }, - entities: [], - }, - { - id: 'entity-b', - aggregation: [5_000, 10_000], - change: { - ratio: 1, - amount: 5_000, + { + id: 'entity-b', + aggregation: [5_000, 10_000], + change: { + ratio: 1, + amount: 5_000, + }, + entities: {}, }, - entities: [], - }, - { - id: 'entity-c', - aggregation: [0, 10_000], - change: { - ratio: 10_000, - amount: 10_000, + { + id: 'entity-c', + aggregation: [0, 10_000], + change: { + ratio: 10_000, + amount: 10_000, + }, + entities: {}, }, - entities: [], - }, - ], + ], + }, }; export const SampleCloudDataflowInsights: Entity = { @@ -555,110 +557,118 @@ export const SampleCloudDataflowInsights: Entity = { ratio: 0.58, amount: 58_000, }, - entities: [ - { - id: null, - aggregation: [10_000, 12_000], - change: { - ratio: 0.2, - amount: 2_000, + entities: { + pipeline: [ + { + id: null, + aggregation: [10_000, 12_000], + change: { + ratio: 0.2, + amount: 2_000, + }, + entities: { + SKU: [ + { + id: 'Sample SKU A', + aggregation: [3_000, 4_000], + change: { + ratio: 0.333333, + amount: 1_000, + }, + entities: {}, + }, + { + id: 'Sample SKU B', + aggregation: [7_000, 8_000], + change: { + ratio: 0.14285714, + amount: 1_000, + }, + entities: {}, + }, + ], + }, }, - entities: [ - { - id: 'Sample SKU A', - aggregation: [3_000, 4_000], - change: { - ratio: 0.333333, - amount: 1_000, - }, - entities: [], + { + id: 'entity-a', + aggregation: [60_000, 70_000], + change: { + ratio: 0.16666666666666666, + amount: 10_000, }, - { - id: 'Sample SKU B', - aggregation: [7_000, 8_000], - change: { - ratio: 0.14285714, - amount: 1_000, - }, - entities: [], + entities: { + SKU: [ + { + id: 'Sample SKU A', + aggregation: [20_000, 15_000], + change: { + ratio: -0.25, + amount: -5_000, + }, + entities: {}, + }, + { + id: 'Sample SKU B', + aggregation: [30_000, 35_000], + change: { + ratio: -0.16666666666666666, + amount: -5_000, + }, + entities: {}, + }, + { + id: 'Sample SKU C', + aggregation: [10_000, 20_000], + change: { + ratio: 1, + amount: 10_000, + }, + entities: {}, + }, + ], }, - ], - }, - { - id: 'entity-a', - aggregation: [60_000, 70_000], - change: { - ratio: 0.16666666666666666, - amount: 10_000, }, - entities: [ - { - id: 'Sample SKU A', - aggregation: [20_000, 15_000], - change: { - ratio: -0.25, - amount: -5_000, - }, - entities: [], + { + id: 'entity-b', + aggregation: [12_000, 8_000], + change: { + ratio: -0.33333, + amount: -4_000, }, - { - id: 'Sample SKU B', - aggregation: [30_000, 35_000], - change: { - ratio: -0.16666666666666666, - amount: -5_000, - }, - entities: [], + entities: { + SKU: [ + { + id: 'Sample SKU A', + aggregation: [4_000, 4_000], + change: { + ratio: 0, + amount: 0, + }, + entities: {}, + }, + { + id: 'Sample SKU B', + aggregation: [8_000, 4_000], + change: { + ratio: -0.5, + amount: -4_000, + }, + entities: {}, + }, + ], }, - { - id: 'Sample SKU C', - aggregation: [10_000, 20_000], - change: { - ratio: 1, - amount: 10_000, - }, - entities: [], - }, - ], - }, - { - id: 'entity-b', - aggregation: [12_000, 8_000], - change: { - ratio: -0.33333, - amount: -4_000, }, - entities: [ - { - id: 'Sample SKU A', - aggregation: [4_000, 4_000], - change: { - ratio: 0, - amount: 0, - }, - entities: [], + { + id: 'entity-c', + aggregation: [0, 10_000], + change: { + ratio: 10_000, + amount: 10_000, }, - { - id: 'Sample SKU B', - aggregation: [8_000, 4_000], - change: { - ratio: -0.5, - amount: -4_000, - }, - entities: [], - }, - ], - }, - { - id: 'entity-c', - aggregation: [0, 10_000], - change: { - ratio: 10_000, - amount: 10_000, + entities: {}, }, - entities: [], - }, - ], + ], + }, }; export const SampleCloudStorageInsights: Entity = { @@ -668,91 +678,97 @@ export const SampleCloudStorageInsights: Entity = { ratio: 0, amount: 0, }, - entities: [ - { - id: 'entity-a', - aggregation: [15_000, 20_000], - change: { - ratio: 0.333, - amount: 5_000, + entities: { + bucket: [ + { + id: 'entity-a', + aggregation: [15_000, 20_000], + change: { + ratio: 0.333, + amount: 5_000, + }, + entities: { + SKU: [ + { + id: 'Sample SKU A', + aggregation: [10_000, 11_000], + change: { + ratio: 0.1, + amount: 1_000, + }, + entities: {}, + }, + { + id: 'Sample SKU B', + aggregation: [2_000, 5_000], + change: { + ratio: 1.5, + amount: 3_000, + }, + entities: {}, + }, + { + id: 'Sample SKU C', + aggregation: [3_000, 4_000], + change: { + ratio: 0.3333, + amount: 1_000, + }, + entities: {}, + }, + ], + }, }, - entities: [ - { - id: 'Sample SKU A', - aggregation: [10_000, 11_000], - change: { - ratio: 0.1, - amount: 1_000, - }, - entities: [], + { + id: 'entity-b', + aggregation: [30_000, 25_000], + change: { + ratio: -0.16666, + amount: -5_000, }, - { - id: 'Sample SKU B', - aggregation: [2_000, 5_000], - change: { - ratio: 1.5, - amount: 3_000, - }, - entities: [], + entities: { + SKU: [ + { + id: 'Sample SKU A', + aggregation: [12_000, 13_000], + change: { + ratio: 0.08333333333333333, + amount: 1_000, + }, + entities: {}, + }, + { + id: 'Sample SKU B', + aggregation: [16_000, 12_000], + change: { + ratio: -0.25, + amount: -4_000, + }, + entities: {}, + }, + { + id: 'Sample SKU C', + aggregation: [2_000, 0], + change: { + ratio: -1, + amount: -2000, + }, + entities: {}, + }, + ], }, - { - id: 'Sample SKU C', - aggregation: [3_000, 4_000], - change: { - ratio: 0.3333, - amount: 1_000, - }, - entities: [], - }, - ], - }, - { - id: 'entity-b', - aggregation: [30_000, 25_000], - change: { - ratio: -0.16666, - amount: -5_000, }, - entities: [ - { - id: 'Sample SKU A', - aggregation: [12_000, 13_000], - change: { - ratio: 0.08333333333333333, - amount: 1_000, - }, - entities: [], + { + id: 'entity-c', + aggregation: [0, 0], + change: { + ratio: 0, + amount: 0, }, - { - id: 'Sample SKU B', - aggregation: [16_000, 12_000], - change: { - ratio: -0.25, - amount: -4_000, - }, - entities: [], - }, - { - id: 'Sample SKU C', - aggregation: [2_000, 0], - change: { - ratio: -1, - amount: -2000, - }, - entities: [], - }, - ], - }, - { - id: 'entity-c', - aggregation: [0, 0], - change: { - ratio: 0, - amount: 0, + entities: {}, }, - entities: [], - }, - ], + ], + }, }; export const SampleComputeEngineInsights: Entity = { @@ -762,91 +778,137 @@ export const SampleComputeEngineInsights: Entity = { ratio: 0.125, amount: 10_000, }, - entities: [ - { - id: 'entity-a', - aggregation: [20_000, 10_000], - change: { - ratio: -0.5, - amount: -10_000, + entities: { + service: [ + { + id: 'entity-a', + aggregation: [20_000, 10_000], + change: { + ratio: -0.5, + amount: -10_000, + }, + entities: { + SKU: [ + { + id: 'Sample SKU A', + aggregation: [4_000, 2_000], + change: { + ratio: -0.5, + amount: -2_000, + }, + entities: {}, + }, + { + id: 'Sample SKU B', + aggregation: [7_000, 6_000], + change: { + ratio: -0.14285714285714285, + amount: -1_000, + }, + entities: {}, + }, + { + id: 'Sample SKU C', + aggregation: [9_000, 2_000], + change: { + ratio: -0.7777777777777778, + amount: -7000, + }, + entities: {}, + }, + ], + deployment: [ + { + id: 'Compute Engine', + aggregation: [7_000, 6_000], + change: { + ratio: -0.5, + amount: -2_000, + }, + entities: {}, + }, + { + id: 'Kubernetes', + aggregation: [4_000, 2_000], + change: { + ratio: -0.14285714285714285, + amount: -1_000, + }, + entities: {}, + }, + ], + }, }, - entities: [ - { - id: 'Sample SKU A', - aggregation: [4_000, 2_000], - change: { - ratio: -0.5, - amount: -2_000, - }, - entities: [], + { + id: 'entity-b', + aggregation: [10_000, 20_000], + change: { + ratio: 1, + amount: 10_000, }, - { - id: 'Sample SKU B', - aggregation: [7_000, 6_000], - change: { - ratio: -0.14285714285714285, - amount: -1_000, - }, - entities: [], + entities: { + SKU: [ + { + id: 'Sample SKU A', + aggregation: [1_000, 2_000], + change: { + ratio: 1, + amount: 1_000, + }, + entities: {}, + }, + { + id: 'Sample SKU B', + aggregation: [4_000, 8_000], + change: { + ratio: 1, + amount: 4_000, + }, + entities: {}, + }, + { + id: 'Sample SKU C', + aggregation: [5_000, 10_000], + change: { + ratio: 1, + amount: 5_000, + }, + entities: {}, + }, + ], + deployment: [ + { + id: 'Compute Engine', + aggregation: [7_000, 6_000], + change: { + ratio: -0.5, + amount: -2_000, + }, + entities: {}, + }, + { + id: 'Kubernetes', + aggregation: [4_000, 2_000], + change: { + ratio: -0.14285714285714285, + amount: -1_000, + }, + entities: {}, + }, + ], }, - { - id: 'Sample SKU C', - aggregation: [9_000, 2_000], - change: { - ratio: -0.7777777777777778, - amount: -7000, - }, - entities: [], - }, - ], - }, - { - id: 'entity-b', - aggregation: [10_000, 20_000], - change: { - ratio: 1, - amount: 10_000, }, - entities: [ - { - id: 'Sample SKU A', - aggregation: [1_000, 2_000], - change: { - ratio: 1, - amount: 1_000, - }, - entities: [], + { + id: 'entity-c', + aggregation: [0, 10_000], + change: { + ratio: 10_000, + amount: 10_000, }, - { - id: 'Sample SKU B', - aggregation: [4_000, 8_000], - change: { - ratio: 1, - amount: 4_000, - }, - entities: [], - }, - { - id: 'Sample SKU C', - aggregation: [5_000, 10_000], - change: { - ratio: 1, - amount: 5_000, - }, - entities: [], - }, - ], - }, - { - id: 'entity-c', - aggregation: [0, 10_000], - change: { - ratio: 10_000, - amount: 10_000, + entities: {}, }, - entities: [], - }, - ], + ], + }, }; export const SampleEventsInsights: Entity = { @@ -856,83 +918,88 @@ export const SampleEventsInsights: Entity = { ratio: -0.5, amount: -10_000, }, - entitiesLabel: 'Product', - entities: [ - { - id: 'entity-a', - aggregation: [15_000, 7_000], - change: { - ratio: -0.53333333333, - amount: -8_000, + entities: { + event: [ + { + id: 'entity-a', + aggregation: [15_000, 7_000], + change: { + ratio: -0.53333333333, + amount: -8_000, + }, + entities: { + product: [ + { + id: 'Sample Product A', + aggregation: [5_000, 2_000], + change: { + ratio: -0.6, + amount: -3_000, + }, + entities: {}, + }, + { + id: 'Sample Product B', + aggregation: [7_000, 2_500], + change: { + ratio: -0.64285714285, + amount: -4_500, + }, + entities: {}, + }, + { + id: 'Sample Product C', + aggregation: [3_000, 2_500], + change: { + ratio: -0.16666666666, + amount: -500, + }, + entities: {}, + }, + ], + }, }, - entities: [ - { - id: 'Sample Product A', - aggregation: [5_000, 2_000], - change: { - ratio: -0.6, - amount: -3_000, - }, - entities: [], + { + id: 'entity-b', + aggregation: [5_000, 3_000], + change: { + ratio: -0.4, + amount: -2_000, }, - { - id: 'Sample Product B', - aggregation: [7_000, 2_500], - change: { - ratio: -0.64285714285, - amount: -4_500, - }, - entities: [], + entities: { + product: [ + { + id: 'Sample Product A', + aggregation: [2_000, 1_000], + change: { + ratio: -0.5, + amount: -1_000, + }, + entities: {}, + }, + { + id: 'Sample Product B', + aggregation: [1_000, 1_500], + change: { + ratio: 0.5, + amount: 500, + }, + entities: {}, + }, + { + id: 'Sample Product C', + aggregation: [2_000, 500], + change: { + ratio: -0.75, + amount: -1_500, + }, + entities: {}, + }, + ], }, - { - id: 'Sample Product C', - aggregation: [3_000, 2_500], - change: { - ratio: -0.16666666666, - amount: -500, - }, - entities: [], - }, - ], - }, - { - id: 'entity-b', - aggregation: [5_000, 3_000], - change: { - ratio: -0.4, - amount: -2_000, }, - entities: [ - { - id: 'Sample Product A', - aggregation: [2_000, 1_000], - change: { - ratio: -0.5, - amount: -1_000, - }, - entities: [], - }, - { - id: 'Sample Product B', - aggregation: [1_000, 1_500], - change: { - ratio: 0.5, - amount: 500, - }, - entities: [], - }, - { - id: 'Sample Product C', - aggregation: [2_000, 500], - change: { - ratio: -0.75, - amount: -1_500, - }, - entities: [], - }, - ], - }, - ], + ], + }, }; export function entityOf(product: string): Entity { diff --git a/yarn.lock b/yarn.lock index 44024b5952..f33bf5a34c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5736,6 +5736,11 @@ dependencies: "@types/express" "*" +"@types/pluralize@^0.0.29": + version "0.0.29" + resolved "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c" + integrity sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA== + "@types/prettier@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.0.tgz#dc85454b953178cc6043df5208b9e949b54a3bc4" @@ -18925,6 +18930,11 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + pn@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" From b14cbe28d3093deba54cf8178687c0eafae55331 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 4 Dec 2020 09:27:32 -0700 Subject: [PATCH 05/19] Fix tests --- .../ProductEntityDialog.test.tsx | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.test.tsx index 076edf60a3..99b6c8ae26 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.test.tsx @@ -77,27 +77,23 @@ describe('', () => { open entity={atomicEntity} onClose={jest.fn()} - selectedLabel={null} - setSelectedLabel={jest.fn()} />, ), ), ).toThrow(); }); - it('Should not show tabs for a single sub-entity type', () => { - const { queryByText } = render( + it('Should show a tab for a single sub-entity type', () => { + const { getByText } = render( wrapInTestApp( , ), ); - expect(queryByText('Breakdown by SKU')).not.toBeInTheDocument(); + expect(getByText('Breakdown by SKU')).toBeInTheDocument(); }); it('Should show tabs when multiple sub-entity types exist', () => { @@ -107,8 +103,6 @@ describe('', () => { open entity={multiBreakdownEntity} onClose={jest.fn()} - selectedLabel={null} - setSelectedLabel={jest.fn()} />, ), ); @@ -116,19 +110,4 @@ describe('', () => { expect(getByText('Breakdown by deployment')).toBeInTheDocument(); expect(getByText('sku-1')).toBeInTheDocument(); }); - - it('Shows the pre-selected tab, if provided', () => { - const { getByText } = render( - wrapInTestApp( - , - ), - ); - expect(getByText('d-1')).toBeInTheDocument(); - }); }); From cadb4b531126bbc05e3dd3bc014109a35092189c Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 4 Dec 2020 15:24:40 -0700 Subject: [PATCH 06/19] findFirst -> findAny --- .../components/ProductInsightsCard/ProductInsightsCard.tsx | 4 ++-- .../components/ProductInsightsCard/ProductInsightsChart.tsx | 4 ++-- plugins/cost-insights/src/utils/assert.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx index e4cb475ab5..534ef490c3 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx @@ -36,7 +36,7 @@ import { useLoading, useScroll, } from '../../hooks'; -import { findFirstKey } from '../../utils/assert'; +import { findAnyKey } from '../../utils/assert'; type LoadingProps = (isLoading: boolean) => void; @@ -93,7 +93,7 @@ export const ProductInsightsCard = ({ }, [product, duration, onSelectAsync, dispatchLoadingProduct]); // Only a single entities Record for the root product entity is supported - const entityKey = findFirstKey(entity?.entities); + const entityKey = findAnyKey(entity?.entities); const entities = entityKey ? entity!.entities[entityKey] : []; const subheader = entityKey diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx index d7c796adf3..08a4c5efa2 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx @@ -37,7 +37,7 @@ import { findAlways, notEmpty, isUndefined, - findFirstKey, + findAnyKey, assertAlways, } from '../../utils/assert'; import { formatPeriod, formatPercent } from '../../utils/formatters'; @@ -70,7 +70,7 @@ export const ProductInsightsChart = ({ const layoutClasses = useLayoutStyles(); // Only a single entities Record for the root product entity is supported - const entityLabel = assertAlways(findFirstKey(entity.entities)); + const entityLabel = assertAlways(findAnyKey(entity.entities)); const entities = entity.entities[entityLabel] ?? []; const [activeLabel, setActive] = useState>(); diff --git a/plugins/cost-insights/src/utils/assert.ts b/plugins/cost-insights/src/utils/assert.ts index 39b710ac3d..05ce65197b 100644 --- a/plugins/cost-insights/src/utils/assert.ts +++ b/plugins/cost-insights/src/utils/assert.ts @@ -51,7 +51,7 @@ export function findAlways( return assertAlways(collection.find(callback)); } -export function findFirstKey( +export function findAnyKey( record: Record | undefined, ): string | undefined { return Object.keys(record ?? {}).find(_ => true); From 6f7ee993c448b317dfa13586ae824894da6e0163 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 4 Dec 2020 15:38:36 -0700 Subject: [PATCH 07/19] M/M -> Change --- .../src/components/ProductInsightsCard/ProductEntityTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityTable.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityTable.tsx index 7c239ba30d..5588154ad0 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityTable.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityTable.tsx @@ -135,7 +135,7 @@ export const ProductEntityTable = ({ }, { field: 'ratio', - title: M/M, + title: Change, align: 'right', render: createRenderer('ratio', classes), customSort: createSorter('ratio'), From 8cc65fec5f903138b5e3e956dfe0a915379772e1 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 4 Dec 2020 20:29:11 -0700 Subject: [PATCH 08/19] The embarrassment A developer without A final paren --- plugins/cost-insights/src/types/Entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cost-insights/src/types/Entity.ts b/plugins/cost-insights/src/types/Entity.ts index e3e1618b4a..b49bb596ae 100644 --- a/plugins/cost-insights/src/types/Entity.ts +++ b/plugins/cost-insights/src/types/Entity.ts @@ -34,7 +34,7 @@ export interface Entity { of the total cost **over the same time period**. The root entity is expected to only have _one_ Record consisting of the sub-entities to display in the product panel (keyed by the entity type, such as "service" for - compute entities. + compute entities). The root sub-entities may have multiple breakdowns - for example, a breakdown of an entity cost by SKU vs deployment environment. The sum From d298d506390e84c93051f293acd994c6b620bcf1 Mon Sep 17 00:00:00 2001 From: David Tuite Date: Sat, 5 Dec 2020 21:49:03 +0000 Subject: [PATCH 09/19] Add pagerduty config to helm chart --- contrib/chart/backstage/templates/backend-secret.yaml | 1 + contrib/chart/backstage/values.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/contrib/chart/backstage/templates/backend-secret.yaml b/contrib/chart/backstage/templates/backend-secret.yaml index b340f39d7c..299d893ec4 100644 --- a/contrib/chart/backstage/templates/backend-secret.yaml +++ b/contrib/chart/backstage/templates/backend-secret.yaml @@ -20,4 +20,5 @@ stringData: AZURE_TOKEN: {{ .Values.auth.azure.api.token }} NEW_RELIC_REST_API_KEY: {{ .Values.auth.newRelicRestApiKey }} TRAVISCI_AUTH_TOKEN: {{ .Values.auth.travisciAuthToken }} + PAGERDUTY_TOKEN: {{ .Values.auth.pagerdutyToken }} {{- end }} diff --git a/contrib/chart/backstage/values.yaml b/contrib/chart/backstage/values.yaml index a4a0fadcc2..261f352f93 100644 --- a/contrib/chart/backstage/values.yaml +++ b/contrib/chart/backstage/values.yaml @@ -250,3 +250,4 @@ auth: gitlabToken: g newRelicRestApiKey: r travisciAuthToken: fake-travis-ci-auth-token + pagerdutyToken: h From 047c018c91f2a113896aaf8737abe7da67478842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 6 Dec 2020 15:19:33 +0100 Subject: [PATCH 10/19] catalog-backend: read all relations at once --- .changeset/fluffy-pillows-smile.md | 5 ++ .../src/database/CommonDatabase.ts | 75 +++++++++++++------ .../src/ingestion/HigherOrderOperations.ts | 2 +- 3 files changed, 58 insertions(+), 24 deletions(-) create mode 100644 .changeset/fluffy-pillows-smile.md diff --git a/.changeset/fluffy-pillows-smile.md b/.changeset/fluffy-pillows-smile.md new file mode 100644 index 0000000000..5d15974035 --- /dev/null +++ b/.changeset/fluffy-pillows-smile.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Batch the fetching of relations diff --git a/plugins/catalog-backend/src/database/CommonDatabase.ts b/plugins/catalog-backend/src/database/CommonDatabase.ts index 287756b0a8..e38d3032b4 100644 --- a/plugins/catalog-backend/src/database/CommonDatabase.ts +++ b/plugins/catalog-backend/src/database/CommonDatabase.ts @@ -271,7 +271,7 @@ export class CommonDatabase implements Database { .select('entities.*') .orderBy('full_name', 'asc'); - return Promise.all(rows.map(row => this.toEntityResponse(tx, row))); + return this.toEntityResponses(tx, rows); } async entityByName( @@ -290,7 +290,7 @@ export class CommonDatabase implements Database { return undefined; } - return this.toEntityResponse(tx, rows[0]); + return this.toEntityResponses(tx, rows).then(r => r[0]); } async entityByUid( @@ -307,7 +307,7 @@ export class CommonDatabase implements Database { return undefined; } - return this.toEntityResponse(tx, rows[0]); + return this.toEntityResponses(tx, rows).then(r => r[0]); } async removeEntityByUid(txOpaque: Transaction, uid: string): Promise { @@ -494,31 +494,60 @@ export class CommonDatabase implements Database { }; } - private async toEntityResponse( + private async toEntityResponses( tx: Knex.Transaction, - row: DbEntitiesRow, - ): Promise { - const entity = JSON.parse(row.data) as Entity; - entity.metadata.uid = row.id; - entity.metadata.etag = row.etag; - entity.metadata.generation = Number(row.generation); // cast due to sqlite - + rows: DbEntitiesRow[], + ): Promise { // TODO(Rugvip): This is here because it's simple for now, but we likely // need to refactor this to be more efficient or introduce pagination. - const relations = await tx('entities_relations') - .where({ source_full_name: row.full_name }) - .orderBy(['type', 'target_full_name']) - .select(); + const relations = await this.getRelationsPerFullName( + tx, + rows.map(r => r.full_name), + ); - entity.relations = deduplicateRelations(relations).map(r => ({ - target: parseEntityName(r.target_full_name), - type: r.type, - })); + const result = new Array(); + for (const row of rows) { + const entity = JSON.parse(row.data) as Entity; + entity.metadata.uid = row.id; + entity.metadata.etag = row.etag; + entity.metadata.generation = Number(row.generation); // cast due to sqlite - return { - locationId: row.location_id || undefined, - entity, - }; + entity.relations = (relations[row.full_name] ?? []).map(r => ({ + target: parseEntityName(r.target_full_name), + type: r.type, + })); + + result.push({ + locationId: row.location_id || undefined, + entity, + }); + } + + return result; + } + + // Returns a mapping from e.g. component:default/foo to the relations whose + // source_full_name matches that. + private async getRelationsPerFullName( + tx: Knex.Transaction, + sourceFullNames: string[], + ): Promise> { + const batches = lodash.chunk(lodash.uniq(sourceFullNames), 500); + + const relations = new Array(); + for (const batch of batches) { + relations.push( + ...(await tx('entities_relations') + .whereIn('source_full_name', batch) + .orderBy(['type', 'target_full_name']) + .select()), + ); + } + + return lodash.groupBy( + deduplicateRelations(relations), + r => r.source_full_name, + ); } } diff --git a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts index bc277e1767..ca19a1d1c5 100644 --- a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts +++ b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts @@ -85,7 +85,7 @@ export class HigherOrderOperations implements HigherOrderOperation { // Write if (!previousLocation && !dryRun) { // TODO: We do not include location operations in the dryRun. We might perform - // this operation as a seperate dry run. + // this operation as a separate dry run. await this.locationsCatalog.addLocation(location); } if (readerOutput.entities.length === 0) { From 06dbe707bf63f3bd3f0dadc6167df56a89c6ff51 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 6 Dec 2020 18:40:59 +0100 Subject: [PATCH 11/19] cli: update experimental backend:bundle command to output archives to dist --- .changeset/dull-dingos-dream.md | 5 ++ packages/cli/src/commands/backend/bundle.ts | 61 ++++++++++++++++----- packages/cli/src/lib/packager/index.ts | 14 ++++- 3 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 .changeset/dull-dingos-dream.md diff --git a/.changeset/dull-dingos-dream.md b/.changeset/dull-dingos-dream.md new file mode 100644 index 0000000000..13a5271987 --- /dev/null +++ b/.changeset/dull-dingos-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Update experimental backend bundle command to only output archives to `dist/` instead of a full workspace mirror in `dist-workspace/`. diff --git a/packages/cli/src/commands/backend/bundle.ts b/packages/cli/src/commands/backend/bundle.ts index e604ae9733..4d298f3735 100644 --- a/packages/cli/src/commands/backend/bundle.ts +++ b/packages/cli/src/commands/backend/bundle.ts @@ -14,26 +14,59 @@ * limitations under the License. */ -import { Command } from 'commander'; +import os from 'os'; import fs from 'fs-extra'; +import { resolve as resolvePath } from 'path'; +import tar, { CreateOptions } from 'tar'; +import { Command } from 'commander'; import { createDistWorkspace } from '../../lib/packager'; import { paths } from '../../lib/paths'; import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel'; +import { buildPackage, Output } from '../../lib/builder'; -const PKG_PATH = 'package.json'; -const TARGET_DIR = 'dist-workspace'; +const BUNDLE_FILE = 'bundle.tar.gz'; +const SKELETON_FILE = 'skeleton.tar.gz'; export default async (cmd: Command) => { - const targetDir = paths.resolveTarget(TARGET_DIR); - const pkgPath = paths.resolveTarget(PKG_PATH); - const pkg = await fs.readJson(pkgPath); + const targetDir = paths.resolveTarget('dist'); + const pkg = await fs.readJson(paths.resolveTarget('package.json')); - await fs.remove(targetDir); - await fs.mkdir(targetDir); - await createDistWorkspace([pkg.name], { - targetDir: targetDir, - buildDependencies: Boolean(cmd.build), - parallel: parseParallel(process.env[PARALLEL_ENV_VAR]), - skeleton: 'skeleton.tar', - }); + // We build the target package without generating type declarations. + await buildPackage({ outputs: new Set([Output.cjs]) }); + + const tmpDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-bundle')); + try { + await createDistWorkspace([pkg.name], { + targetDir: tmpDir, + buildDependencies: Boolean(cmd.build), + buildExcludes: [pkg.name], + parallel: parseParallel(process.env[PARALLEL_ENV_VAR]), + skeleton: SKELETON_FILE, + }); + + // We built the target backend package using the regular build process, but the result of + // that has now been packed into the dist workspace, so clean up the dist dir. + await fs.remove(targetDir); + await fs.mkdir(targetDir); + + // Move out skeleton.tar.gz before we create the main bundle, no point having that included up twice. + await fs.move( + resolvePath(tmpDir, SKELETON_FILE), + resolvePath(targetDir, SKELETON_FILE), + ); + + // Create main bundle.tar.gz, with some tweaks to make it more likely hit Docker build cache. + await tar.create( + { + file: resolvePath(targetDir, BUNDLE_FILE), + cwd: tmpDir, + portable: true, + noMtime: true, + gzip: true, + } as CreateOptions & { noMtime: boolean }, + [''], + ); + } finally { + await fs.remove(tmpDir); + } }; diff --git a/packages/cli/src/lib/packager/index.ts b/packages/cli/src/lib/packager/index.ts index 8653e66d37..e9388ad9bc 100644 --- a/packages/cli/src/lib/packager/index.ts +++ b/packages/cli/src/lib/packager/index.ts @@ -67,6 +67,11 @@ type Options = { */ buildDependencies?: boolean; + /** + * When `buildDependencies` is set, this list of packages will not be built even if they are dependencies. + */ + buildExcludes?: string[]; + /** * Enable (true/false) or control amount of (number) parallelism in some build steps. */ @@ -76,7 +81,7 @@ type Options = { * If set, creates a skeleton tarball that contains all package.json files * with the same structure as the workspace dir. */ - skeleton?: 'skeleton.tar'; + skeleton?: 'skeleton.tar' | 'skeleton.tar.gz'; }; /** @@ -98,7 +103,11 @@ export async function createDistWorkspace( const targets = await findTargetPackages(packageNames); if (options.buildDependencies) { - const scopeArgs = targets.flatMap(target => ['--scope', target.name]); + const exclude = options.buildExcludes ?? []; + const scopeArgs = targets + .filter(target => !exclude.includes(target.name)) + .flatMap(target => ['--scope', target.name]); + const lernaArgs = options.parallel && Number.isInteger(options.parallel) ? ['--concurrency', options.parallel.toString()] @@ -131,6 +140,7 @@ export async function createDistWorkspace( cwd: targetDir, portable: true, noMtime: true, + gzip: options.skeleton.endsWith('.gz'), } as CreateOptions & { noMtime: boolean }, skeletonFiles, ); From 94348441e0bf6c3fc51651ff0afb4f59a10ac6ba Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 6 Dec 2020 22:50:20 +0100 Subject: [PATCH 12/19] app,backend,create-app: add files declaration to package.jsons --- .changeset/late-tomatoes-sleep.md | 13 +++++++++++++ packages/app/package.json | 3 +++ packages/backend/package.json | 5 ++++- .../default-app/packages/app/package.json.hbs | 5 ++++- .../default-app/packages/backend/package.json.hbs | 5 ++++- 5 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 .changeset/late-tomatoes-sleep.md diff --git a/.changeset/late-tomatoes-sleep.md b/.changeset/late-tomatoes-sleep.md new file mode 100644 index 0000000000..f369b626f0 --- /dev/null +++ b/.changeset/late-tomatoes-sleep.md @@ -0,0 +1,13 @@ +--- +'@backstage/create-app': patch +--- + +Add `"files": ["dist"]` to both app and backend packages. This ensures that packaged versions of these packages do not contain unnecessary files. + +To apply this change to an existing app, add the following to `packages/app/package.json` and `packages/backend/package.json`: + +```json + "files": [ + "dist" + ] +``` diff --git a/packages/app/package.json b/packages/app/package.json index afe35ff382..e671e23b79 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -89,5 +89,8 @@ "last 1 safari version" ] }, + "files": [ + "dist" + ], "license": "Apache-2.0" } diff --git a/packages/backend/package.json b/packages/backend/package.json index 5937d6a6f3..5fbde0a110 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -50,5 +50,8 @@ "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", "@types/helmet": "^0.0.48" - } + }, + "files": [ + "dist" + ] } diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index 3f04773e31..f284f2da0f 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -61,5 +61,8 @@ "last 1 firefox version", "last 1 safari version" ] - } + }, + "files": [ + "dist" + ] } diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 8dccb0b683..2c8f460a35 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -47,5 +47,8 @@ "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", "@types/helmet": "^0.0.47" - } + }, + "files": [ + "dist" + ] } From 2c12b5fc79e4a22ef2ff390723372bb4bd5a5b05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Dec 2020 09:42:58 +0100 Subject: [PATCH 13/19] build(deps-dev): bump @types/testing-library__jest-dom (#3596) Bumps [@types/testing-library__jest-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/testing-library__jest-dom) from 5.9.1 to 5.9.5. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/testing-library__jest-dom) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 66 +++++-------------------------------------------------- 1 file changed, 5 insertions(+), 61 deletions(-) diff --git a/yarn.lock b/yarn.lock index a0681a459b..8beba6c7d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2481,16 +2481,6 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" -"@jest/types@^25.5.0": - version "25.5.0" - resolved "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" - integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - "@jest/types@^26.5.2", "@jest/types@^26.6.1": version "26.6.1" resolved "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz#2638890e8031c0bc8b4681e0357ed986e2f866c5" @@ -5388,14 +5378,6 @@ dependencies: "@types/istanbul-lib-coverage" "*" -"@types/istanbul-reports@^1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" - integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== - dependencies: - "@types/istanbul-lib-coverage" "*" - "@types/istanbul-lib-report" "*" - "@types/istanbul-reports@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" @@ -5403,15 +5385,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*", "@types/jest@^26.0.7": - version "26.0.14" - resolved "https://registry.npmjs.org/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3" - integrity sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg== - dependencies: - jest-diff "^25.2.1" - pretty-format "^25.2.1" - -"@types/jest@26.x": +"@types/jest@*", "@types/jest@26.x", "@types/jest@^26.0.7": version "26.0.15" resolved "https://registry.npmjs.org/@types/jest/-/jest-26.0.15.tgz#12e02c0372ad0548e07b9f4e19132b834cb1effe" integrity sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog== @@ -6030,9 +6004,9 @@ "@types/estree" "*" "@types/testing-library__jest-dom@^5.9.1": - version "5.9.1" - resolved "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.1.tgz#aba5ee062b7880f69c212ef769389f30752806e5" - integrity sha512-yYn5EKHO3MPEMSOrcAb1dLWY+68CG29LiXKsWmmpVHqoP5+ZRiAVLyUHvPNrO2dABDdUGZvavMsaGpWNjM6N2g== + version "5.9.5" + resolved "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz#5bf25c91ad2d7b38f264b12275e5c92a66d849b0" + integrity sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ== dependencies: "@types/jest" "*" @@ -10552,11 +10526,6 @@ dicer@0.3.0: dependencies: streamsearch "0.1.2" -diff-sequences@^25.2.6: - version "25.2.6" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" - integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== - diff-sequences@^26.5.0: version "26.5.0" resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz#ef766cf09d43ed40406611f11c6d8d9dd8b2fefd" @@ -14797,16 +14766,6 @@ jest-css-modules@^2.1.0: dependencies: identity-obj-proxy "3.0.0" -jest-diff@^25.2.1: - version "25.5.0" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" - integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== - dependencies: - chalk "^3.0.0" - diff-sequences "^25.2.6" - jest-get-type "^25.2.6" - pretty-format "^25.5.0" - jest-diff@^26.0.0, jest-diff@^26.5.2: version "26.6.1" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.1.tgz#38aa194979f454619bb39bdee299fb64ede5300c" @@ -14868,11 +14827,6 @@ jest-esm-transformer@^1.0.0: "@babel/core" "^7.4.4" "@babel/plugin-transform-modules-commonjs" "^7.4.4" -jest-get-type@^25.2.6: - version "25.2.6" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" - integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== - jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" @@ -19443,16 +19397,6 @@ pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@^25.2.1, pretty-format@^25.5.0: - version "25.5.0" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" - integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== - dependencies: - "@jest/types" "^25.5.0" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - pretty-format@^26.0.0, pretty-format@^26.4.2, pretty-format@^26.5.2, pretty-format@^26.6.1: version "26.6.1" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.1.tgz#af9a2f63493a856acddeeb11ba6bcf61989660a8" @@ -20118,7 +20062,7 @@ react-inspector@^5.0.1: is-dom "^1.1.0" prop-types "^15.6.1" -react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0: +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== From b20a63d103a701f01d7d0519eeb7b444fb9fde3a Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Mon, 7 Dec 2020 03:44:25 -0500 Subject: [PATCH 14/19] fix(lighthouse): Typo & example code tweaks (#3573) * Update export example * Fix typo --- plugins/lighthouse/README.md | 5 ++--- plugins/lighthouse/src/components/Intro/index.tsx | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/lighthouse/README.md b/plugins/lighthouse/README.md index cdcc2fe2c4..1f1d32937f 100644 --- a/plugins/lighthouse/README.md +++ b/plugins/lighthouse/README.md @@ -27,8 +27,7 @@ your app's [`plugins.ts`](https://github.com/backstage/backstage/blob/master/pac to enable the plugin: ```js -import { default as LighthousePlugin } from '@backstage/plugin-lighthouse'; -export LighthousePlugin; +export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse'; ``` Then, you need to use the `lighthouseApiRef` exported from the plugin to initialize the Rest API in @@ -74,7 +73,7 @@ metadata: lighthouse.com/website-url: # A single website url e.g. https://backstage.io/ ``` -> NOTE: The lighthouse plugin only supports one website url per component at this time. +> NOTE: The lighthouse plugin only supports one website URL per component at this time. Add a lighthouse tab to the EntityPage: diff --git a/plugins/lighthouse/src/components/Intro/index.tsx b/plugins/lighthouse/src/components/Intro/index.tsx index 3e969a3c74..b140141c9f 100644 --- a/plugins/lighthouse/src/components/Intro/index.tsx +++ b/plugins/lighthouse/src/components/Intro/index.tsx @@ -26,7 +26,7 @@ export const LIGHTHOUSE_INTRO_LOCAL_STORAGE = const USE_CASES = ` Google's [Lighthouse](https://developers.google.com/web/tools/lighthouse) auditing tool for websites -is a great open-source resource forbenchmarking and improving the accessibility, performance, SEO, and best practices of your site. +is a great open-source resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your site. At Spotify, we keep track of Lighthouse audit scores over time to look at trends and overall areas for investment. This plugin allows you to generate on-demand Lighthouse audits for websites, and to track the trends for the @@ -46,8 +46,7 @@ your app's [\`plugins.ts\`](https://github.com/backstage/backstage/blob/master/p to enable the plugin: \`\`\`js -import { default as LighthousePlugin } from '@backstage/plugin-lighthouse'; -export LighthousePlugin; +export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse'; \`\`\` Then, you need to use the \`lighthouseApiRef\` exported from the plugin to initialize the Rest API in From ad6ef68f2b9590c9bddf0dc0d53783c06e13be3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Dec 2020 09:45:10 +0100 Subject: [PATCH 15/19] build(deps-dev): bump docusaurus in /microsite (#3543) Bumps [docusaurus](https://github.com/facebook/docusaurus) from 2.0.0-alpha.66 to 2.0.0-alpha.378053ac5. - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/master/CHANGELOG-2.x.md) - [Commits](https://github.com/facebook/docusaurus/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- microsite/package.json | 2 +- microsite/yarn.lock | 1068 +++++++++++++++++++++------------------- 2 files changed, 569 insertions(+), 501 deletions(-) diff --git a/microsite/package.json b/microsite/package.json index 6be0579475..b0390e8c20 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@spotify/prettier-config": "^9.0.0", - "docusaurus": "^2.0.0-alpha.66", + "docusaurus": "^2.0.0-alpha.378053ac5", "js-yaml": "^3.14.0", "prettier": "^2.2.1" }, diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 22efeb7a24..02e7be47ef 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -16,28 +16,24 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/compat-data@^7.10.4", "@babel/compat-data@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz#e9f73efe09af1355b723a7f39b11bad637d7c99c" - integrity sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ== - dependencies: - browserslist "^4.12.0" - invariant "^2.2.4" - semver "^5.5.0" +"@babel/compat-data@^7.12.5", "@babel/compat-data@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.7.tgz#9329b4782a7d6bbd7eef57e11addf91ee3ef1e41" + integrity sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw== -"@babel/core@^7.9.0": - version "7.11.6" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651" - integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg== +"@babel/core@^7.12.3": + version "7.12.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" + integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.6" - "@babel/helper-module-transforms" "^7.11.0" - "@babel/helpers" "^7.10.4" - "@babel/parser" "^7.11.5" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.11.5" - "@babel/types" "^7.11.5" + "@babel/generator" "^7.12.5" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.5" + "@babel/parser" "^7.12.7" + "@babel/template" "^7.12.7" + "@babel/traverse" "^7.12.9" + "@babel/types" "^7.12.7" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" @@ -47,12 +43,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.11.5", "@babel/generator@^7.11.6": - version "7.11.6" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" - integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA== +"@babel/generator@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.5.tgz#a2c50de5c8b6d708ab95be5e6053936c1884a4de" + integrity sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A== dependencies: - "@babel/types" "^7.11.5" + "@babel/types" "^7.12.5" jsesc "^2.5.1" source-map "^0.5.0" @@ -71,14 +67,14 @@ "@babel/helper-explode-assignable-expression" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-builder-react-jsx-experimental@^7.10.4", "@babel/helper-builder-react-jsx-experimental@^7.11.5": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.11.5.tgz#4ea43dd63857b0a35cd1f1b161dc29b43414e79f" - integrity sha512-Vc4aPJnRZKWfzeCBsqTBnzulVNjABVdahSPhtdMD3Vs80ykx4a87jTHtF/VR+alSrDmNvat7l13yrRHauGcHVw== +"@babel/helper-builder-react-jsx-experimental@^7.12.4": + version "7.12.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.4.tgz#55fc1ead5242caa0ca2875dcb8eed6d311e50f48" + integrity sha512-AjEa0jrQqNk7eDQOo0pTfUOwQBMF+xVqrausQwT9/rTKy0g04ggFNaJpaE09IQMn9yExluigWMJcj0WC7bq+Og== dependencies: "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-module-imports" "^7.10.4" - "@babel/types" "^7.11.5" + "@babel/helper-module-imports" "^7.12.1" + "@babel/types" "^7.12.1" "@babel/helper-builder-react-jsx@^7.10.4": version "7.10.4" @@ -88,27 +84,25 @@ "@babel/helper-annotate-as-pure" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-compilation-targets@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" - integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ== +"@babel/helper-compilation-targets@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz#cb470c76198db6a24e9dbc8987275631e5d29831" + integrity sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw== dependencies: - "@babel/compat-data" "^7.10.4" - browserslist "^4.12.0" - invariant "^2.2.4" - levenary "^1.1.1" + "@babel/compat-data" "^7.12.5" + "@babel/helper-validator-option" "^7.12.1" + browserslist "^4.14.5" semver "^5.5.0" -"@babel/helper-create-class-features-plugin@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" - integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A== +"@babel/helper-create-class-features-plugin@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz#3c45998f431edd4a9214c5f1d3ad1448a6137f6e" + integrity sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w== dependencies: "@babel/helper-function-name" "^7.10.4" - "@babel/helper-member-expression-to-functions" "^7.10.5" + "@babel/helper-member-expression-to-functions" "^7.12.1" "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-replace-supers" "^7.12.1" "@babel/helper-split-export-declaration" "^7.10.4" "@babel/helper-create-regexp-features-plugin@^7.10.4": @@ -120,6 +114,14 @@ "@babel/helper-regex" "^7.10.4" regexpu-core "^4.7.0" +"@babel/helper-create-regexp-features-plugin@^7.12.1": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz#2084172e95443fa0a09214ba1bb328f9aea1278f" + integrity sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + regexpu-core "^4.7.1" + "@babel/helper-define-map@^7.10.4": version "7.10.5" resolved "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30" @@ -159,31 +161,33 @@ dependencies: "@babel/types" "^7.10.4" -"@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" - integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q== +"@babel/helper-member-expression-to-functions@^7.12.1": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz#aa77bd0396ec8114e5e30787efa78599d874a855" + integrity sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw== dependencies: - "@babel/types" "^7.11.0" + "@babel/types" "^7.12.7" -"@babel/helper-module-imports@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" - integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== +"@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb" + integrity sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA== dependencies: - "@babel/types" "^7.10.4" + "@babel/types" "^7.12.5" -"@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" - integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg== +"@babel/helper-module-transforms@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c" + integrity sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w== dependencies: - "@babel/helper-module-imports" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" - "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-module-imports" "^7.12.1" + "@babel/helper-replace-supers" "^7.12.1" + "@babel/helper-simple-access" "^7.12.1" "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/helper-validator-identifier" "^7.10.4" "@babel/template" "^7.10.4" - "@babel/types" "^7.11.0" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" lodash "^4.17.19" "@babel/helper-optimise-call-expression@^7.10.4": @@ -205,40 +209,38 @@ dependencies: lodash "^4.17.19" -"@babel/helper-remap-async-to-generator@^7.10.4": - version "7.11.4" - resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz#4474ea9f7438f18575e30b0cac784045b402a12d" - integrity sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA== +"@babel/helper-remap-async-to-generator@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz#8c4dbbf916314f6047dc05e6a2217074238347fd" + integrity sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A== dependencies: "@babel/helper-annotate-as-pure" "^7.10.4" "@babel/helper-wrap-function" "^7.10.4" - "@babel/template" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/types" "^7.12.1" -"@babel/helper-replace-supers@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" - integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== +"@babel/helper-replace-supers@^7.12.1": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz#f009a17543bbbbce16b06206ae73b63d3fca68d9" + integrity sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA== dependencies: - "@babel/helper-member-expression-to-functions" "^7.10.4" + "@babel/helper-member-expression-to-functions" "^7.12.1" "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/traverse" "^7.12.5" + "@babel/types" "^7.12.5" -"@babel/helper-simple-access@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" - integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== +"@babel/helper-simple-access@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz#32427e5aa61547d38eb1e6eaf5fd1426fdad9136" + integrity sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA== dependencies: - "@babel/template" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/types" "^7.12.1" -"@babel/helper-skip-transparent-expression-wrappers@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729" - integrity sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q== +"@babel/helper-skip-transparent-expression-wrappers@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf" + integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA== dependencies: - "@babel/types" "^7.11.0" + "@babel/types" "^7.12.1" "@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": version "7.11.0" @@ -252,6 +254,11 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== +"@babel/helper-validator-option@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz#175567380c3e77d60ff98a54bb015fe78f2178d9" + integrity sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A== + "@babel/helper-wrap-function@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" @@ -262,14 +269,14 @@ "@babel/traverse" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helpers@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" - integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== +"@babel/helpers@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.5.tgz#1a1ba4a768d9b58310eda516c449913fe647116e" + integrity sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA== dependencies: "@babel/template" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/traverse" "^7.12.5" + "@babel/types" "^7.12.5" "@babel/highlight@^7.0.0", "@babel/highlight@^7.10.4": version "7.10.4" @@ -280,111 +287,124 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.10.4", "@babel/parser@^7.11.5": +"@babel/parser@^7.10.4": version "7.11.5" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== -"@babel/plugin-proposal-async-generator-functions@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558" - integrity sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg== +"@babel/parser@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.7.tgz#fee7b39fe809d0e73e5b25eecaf5780ef3d73056" + integrity sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg== + +"@babel/plugin-proposal-async-generator-functions@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz#dc6c1170e27d8aca99ff65f4925bd06b1c90550e" + integrity sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A== dependencies: "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-remap-async-to-generator" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.12.1" "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/plugin-proposal-class-properties@^7.10.4", "@babel/plugin-proposal-class-properties@^7.8.3": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" - integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== +"@babel/plugin-proposal-class-properties@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz#a082ff541f2a29a4821065b8add9346c0c16e5de" + integrity sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w== dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-create-class-features-plugin" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-proposal-dynamic-import@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" - integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ== +"@babel/plugin-proposal-dynamic-import@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz#43eb5c2a3487ecd98c5c8ea8b5fdb69a2749b2dc" + integrity sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-dynamic-import" "^7.8.0" -"@babel/plugin-proposal-export-namespace-from@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz#570d883b91031637b3e2958eea3c438e62c05f54" - integrity sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg== +"@babel/plugin-proposal-export-namespace-from@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz#8b9b8f376b2d88f5dd774e4d24a5cc2e3679b6d4" + integrity sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-json-strings@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db" - integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw== +"@babel/plugin-proposal-json-strings@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz#d45423b517714eedd5621a9dfdc03fa9f4eb241c" + integrity sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.0" -"@babel/plugin-proposal-logical-assignment-operators@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz#9f80e482c03083c87125dee10026b58527ea20c8" - integrity sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q== +"@babel/plugin-proposal-logical-assignment-operators@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz#f2c490d36e1b3c9659241034a5d2cd50263a2751" + integrity sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" - integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz#3ed4fff31c015e7f3f1467f190dbe545cd7b046c" + integrity sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" -"@babel/plugin-proposal-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06" - integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA== +"@babel/plugin-proposal-numeric-separator@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz#8bf253de8139099fea193b297d23a9d406ef056b" + integrity sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.11.0", "@babel/plugin-proposal-object-rest-spread@^7.9.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz#bd81f95a1f746760ea43b6c2d3d62b11790ad0af" - integrity sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA== +"@babel/plugin-proposal-object-rest-spread@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069" + integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-transform-parameters" "^7.12.1" -"@babel/plugin-proposal-optional-catch-binding@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd" - integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g== +"@babel/plugin-proposal-optional-catch-binding@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz#ccc2421af64d3aae50b558a71cede929a5ab2942" + integrity sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076" - integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA== +"@babel/plugin-proposal-optional-chaining@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz#e02f0ea1b5dc59d401ec16fb824679f683d3303c" + integrity sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-proposal-private-methods@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909" - integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw== +"@babel/plugin-proposal-private-methods@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz#86814f6e7a21374c980c10d38b4493e703f4a389" + integrity sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w== dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-create-class-features-plugin" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-proposal-unicode-property-regex@^7.10.4", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": +"@babel/plugin-proposal-unicode-property-regex@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz#2a183958d417765b9eae334f47758e5d6a82e072" + integrity sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d" integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA== @@ -399,10 +419,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c" - integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA== +"@babel/plugin-syntax-class-properties@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz#bcb297c5366e79bebadef509549cd93b04f19978" + integrity sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" @@ -427,10 +447,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz#39abaae3cbf710c4373d8429484e6ba21340166c" - integrity sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g== +"@babel/plugin-syntax-jsx@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926" + integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== dependencies: "@babel/helper-plugin-utils" "^7.10.4" @@ -476,72 +496,80 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d" - integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ== +"@babel/plugin-syntax-top-level-await@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0" + integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-arrow-functions@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd" - integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA== +"@babel/plugin-transform-arrow-functions@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz#8083ffc86ac8e777fbe24b5967c4b2521f3cb2b3" + integrity sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-async-to-generator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37" - integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ== +"@babel/plugin-transform-async-to-generator@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz#3849a49cc2a22e9743cbd6b52926d30337229af1" + integrity sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A== dependencies: - "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-module-imports" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-remap-async-to-generator" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.12.1" -"@babel/plugin-transform-block-scoped-functions@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8" - integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA== +"@babel/plugin-transform-block-scoped-functions@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz#f2a1a365bde2b7112e0a6ded9067fdd7c07905d9" + integrity sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-block-scoping@^7.10.4": - version "7.11.1" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz#5b7efe98852bef8d652c0b28144cd93a9e4b5215" - integrity sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew== +"@babel/plugin-transform-block-scoping@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz#f0ee727874b42a208a48a586b84c3d222c2bbef1" + integrity sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-classes@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7" - integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA== +"@babel/plugin-transform-classes@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz#65e650fcaddd3d88ddce67c0f834a3d436a32db6" + integrity sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog== dependencies: "@babel/helper-annotate-as-pure" "^7.10.4" "@babel/helper-define-map" "^7.10.4" "@babel/helper-function-name" "^7.10.4" "@babel/helper-optimise-call-expression" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-replace-supers" "^7.12.1" "@babel/helper-split-export-declaration" "^7.10.4" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb" - integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw== +"@babel/plugin-transform-computed-properties@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz#d68cf6c9b7f838a8a4144badbe97541ea0904852" + integrity sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-destructuring@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5" - integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA== +"@babel/plugin-transform-destructuring@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz#b9a570fe0d0a8d460116413cb4f97e8e08b2f847" + integrity sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-dotall-regex@^7.10.4", "@babel/plugin-transform-dotall-regex@^7.4.4": +"@babel/plugin-transform-dotall-regex@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz#a1d16c14862817b6409c0a678d6f9373ca9cd975" + integrity sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-dotall-regex@^7.4.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee" integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA== @@ -549,273 +577,270 @@ "@babel/helper-create-regexp-features-plugin" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-duplicate-keys@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47" - integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA== +"@babel/plugin-transform-duplicate-keys@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz#745661baba295ac06e686822797a69fbaa2ca228" + integrity sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-exponentiation-operator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e" - integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw== +"@babel/plugin-transform-exponentiation-operator@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz#b0f2ed356ba1be1428ecaf128ff8a24f02830ae0" + integrity sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug== dependencies: "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-for-of@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9" - integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ== +"@babel/plugin-transform-for-of@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz#07640f28867ed16f9511c99c888291f560921cfa" + integrity sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-function-name@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7" - integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg== +"@babel/plugin-transform-function-name@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz#2ec76258c70fe08c6d7da154003a480620eba667" + integrity sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw== dependencies: "@babel/helper-function-name" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-literals@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c" - integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ== +"@babel/plugin-transform-literals@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz#d73b803a26b37017ddf9d3bb8f4dc58bfb806f57" + integrity sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-member-expression-literals@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7" - integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw== +"@babel/plugin-transform-member-expression-literals@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz#496038602daf1514a64d43d8e17cbb2755e0c3ad" + integrity sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-modules-amd@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz#1b9cddaf05d9e88b3aad339cb3e445c4f020a9b1" - integrity sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw== +"@babel/plugin-transform-modules-amd@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz#3154300b026185666eebb0c0ed7f8415fefcf6f9" + integrity sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ== dependencies: - "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-module-transforms" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0" - integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w== +"@babel/plugin-transform-modules-commonjs@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz#fa403124542636c786cf9b460a0ffbb48a86e648" + integrity sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag== dependencies: - "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-module-transforms" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-simple-access" "^7.12.1" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85" - integrity sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw== +"@babel/plugin-transform-modules-systemjs@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz#663fea620d593c93f214a464cd399bf6dc683086" + integrity sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q== dependencies: "@babel/helper-hoist-variables" "^7.10.4" - "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-module-transforms" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-validator-identifier" "^7.10.4" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-umd@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e" - integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA== +"@babel/plugin-transform-modules-umd@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz#eb5a218d6b1c68f3d6217b8fa2cc82fec6547902" + integrity sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q== dependencies: - "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-module-transforms" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6" - integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA== +"@babel/plugin-transform-named-capturing-groups-regex@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz#b407f5c96be0d9f5f88467497fa82b30ac3e8753" + integrity sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-create-regexp-features-plugin" "^7.12.1" -"@babel/plugin-transform-new-target@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888" - integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw== +"@babel/plugin-transform-new-target@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz#80073f02ee1bb2d365c3416490e085c95759dec0" + integrity sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-object-super@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894" - integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ== +"@babel/plugin-transform-object-super@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz#4ea08696b8d2e65841d0c7706482b048bed1066e" + integrity sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-replace-supers" "^7.12.1" -"@babel/plugin-transform-parameters@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz#59d339d58d0b1950435f4043e74e2510005e2c4a" - integrity sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== - dependencies: - "@babel/helper-get-function-arity" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-property-literals@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0" - integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g== +"@babel/plugin-transform-parameters@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz#d2e963b038771650c922eff593799c96d853255d" + integrity sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-display-name@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz#b5795f4e3e3140419c3611b7a2a3832b9aef328d" - integrity sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw== +"@babel/plugin-transform-property-literals@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz#41bc81200d730abb4456ab8b3fbd5537b59adecd" + integrity sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-jsx-development@^7.10.4": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.11.5.tgz#e1439e6a57ee3d43e9f54ace363fb29cefe5d7b6" - integrity sha512-cImAmIlKJ84sDmpQzm4/0q/2xrXlDezQoixy3qoz1NJeZL/8PRon6xZtluvr4H4FzwlDGI5tCcFupMnXGtr+qw== - dependencies: - "@babel/helper-builder-react-jsx-experimental" "^7.11.5" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx" "^7.10.4" - -"@babel/plugin-transform-react-jsx-self@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz#cd301a5fed8988c182ed0b9d55e9bd6db0bd9369" - integrity sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg== +"@babel/plugin-transform-react-display-name@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz#1cbcd0c3b1d6648c55374a22fc9b6b7e5341c00d" + integrity sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w== dependencies: "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx" "^7.10.4" -"@babel/plugin-transform-react-jsx-source@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz#34f1779117520a779c054f2cdd9680435b9222b4" - integrity sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA== +"@babel/plugin-transform-react-jsx-development@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.7.tgz#4c2a647de79c7e2b16bfe4540677ba3121e82a08" + integrity sha512-Rs3ETtMtR3VLXFeYRChle5SsP/P9Jp/6dsewBQfokDSzKJThlsuFcnzLTDRALiUmTC48ej19YD9uN1mupEeEDg== + dependencies: + "@babel/helper-builder-react-jsx-experimental" "^7.12.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.12.1" + +"@babel/plugin-transform-react-jsx-self@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.1.tgz#ef43cbca2a14f1bd17807dbe4376ff89d714cf28" + integrity sha512-FbpL0ieNWiiBB5tCldX17EtXgmzeEZjFrix72rQYeq9X6nUK38HCaxexzVQrZWXanxKJPKVVIU37gFjEQYkPkA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx" "^7.10.4" -"@babel/plugin-transform-react-jsx@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz#673c9f913948764a4421683b2bef2936968fddf2" - integrity sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A== +"@babel/plugin-transform-react-jsx-source@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.1.tgz#d07de6863f468da0809edcf79a1aa8ce2a82a26b" + integrity sha512-keQ5kBfjJNRc6zZN1/nVHCd6LLIHq4aUKcVnvE/2l+ZZROSbqoiGFRtT5t3Is89XJxBQaP7NLZX2jgGHdZvvFQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-react-jsx@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.7.tgz#8b14d45f6eccd41b7f924bcb65c021e9f0a06f7f" + integrity sha512-YFlTi6MEsclFAPIDNZYiCRbneg1MFGao9pPG9uD5htwE0vDbPaMUMeYd6itWjw7K4kro4UbdQf3ljmFl9y48dQ== dependencies: "@babel/helper-builder-react-jsx" "^7.10.4" - "@babel/helper-builder-react-jsx-experimental" "^7.10.4" + "@babel/helper-builder-react-jsx-experimental" "^7.12.4" "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.12.1" -"@babel/plugin-transform-react-pure-annotations@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz#3eefbb73db94afbc075f097523e445354a1c6501" - integrity sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A== +"@babel/plugin-transform-react-pure-annotations@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz#05d46f0ab4d1339ac59adf20a1462c91b37a1a42" + integrity sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg== dependencies: "@babel/helper-annotate-as-pure" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-regenerator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" - integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw== +"@babel/plugin-transform-regenerator@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz#5f0a28d842f6462281f06a964e88ba8d7ab49753" + integrity sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng== dependencies: regenerator-transform "^0.14.2" -"@babel/plugin-transform-reserved-words@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd" - integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ== +"@babel/plugin-transform-reserved-words@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz#6fdfc8cc7edcc42b36a7c12188c6787c873adcd8" + integrity sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-shorthand-properties@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" - integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== +"@babel/plugin-transform-shorthand-properties@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz#0bf9cac5550fce0cfdf043420f661d645fdc75e3" + integrity sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-spread@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz#fa84d300f5e4f57752fe41a6d1b3c554f13f17cc" - integrity sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw== +"@babel/plugin-transform-spread@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz#527f9f311be4ec7fdc2b79bb89f7bf884b3e1e1e" + integrity sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng== dependencies: "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" -"@babel/plugin-transform-sticky-regex@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d" - integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-regex" "^7.10.4" - -"@babel/plugin-transform-template-literals@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz#78bc5d626a6642db3312d9d0f001f5e7639fde8c" - integrity sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-typeof-symbol@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc" - integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA== +"@babel/plugin-transform-sticky-regex@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz#560224613ab23987453948ed21d0b0b193fa7fad" + integrity sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-unicode-escapes@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" - integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg== +"@babel/plugin-transform-template-literals@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz#b43ece6ed9a79c0c71119f576d299ef09d942843" + integrity sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-unicode-regex@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8" - integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A== +"@babel/plugin-transform-typeof-symbol@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz#9ca6be343d42512fbc2e68236a82ae64bc7af78a" + integrity sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/polyfill@^7.8.7": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.11.5.tgz#df550b2ec53abbc2ed599367ec59e64c7a707bb5" - integrity sha512-FunXnE0Sgpd61pKSj2OSOs1D44rKTD3pGOfGilZ6LGrrIH0QEtJlTjqOqdF8Bs98JmjfGhni2BBkTfv9KcKJ9g== +"@babel/plugin-transform-unicode-escapes@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz#5232b9f81ccb07070b7c3c36c67a1b78f1845709" + integrity sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-unicode-regex@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz#cc9661f61390db5c65e3febaccefd5c6ac3faecb" + integrity sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/polyfill@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96" + integrity sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g== dependencies: core-js "^2.6.5" regenerator-runtime "^0.13.4" -"@babel/preset-env@^7.9.0": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz#18cb4b9379e3e92ffea92c07471a99a2914e4272" - integrity sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA== +"@babel/preset-env@^7.12.1": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.7.tgz#54ea21dbe92caf6f10cb1a0a576adc4ebf094b55" + integrity sha512-OnNdfAr1FUQg7ksb7bmbKoby4qFOHw6DKWWUNB9KqnnCldxhxJlP+21dpyaWFmf2h0rTbOkXJtAGevY3XW1eew== dependencies: - "@babel/compat-data" "^7.11.0" - "@babel/helper-compilation-targets" "^7.10.4" - "@babel/helper-module-imports" "^7.10.4" + "@babel/compat-data" "^7.12.7" + "@babel/helper-compilation-targets" "^7.12.5" + "@babel/helper-module-imports" "^7.12.5" "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-proposal-async-generator-functions" "^7.10.4" - "@babel/plugin-proposal-class-properties" "^7.10.4" - "@babel/plugin-proposal-dynamic-import" "^7.10.4" - "@babel/plugin-proposal-export-namespace-from" "^7.10.4" - "@babel/plugin-proposal-json-strings" "^7.10.4" - "@babel/plugin-proposal-logical-assignment-operators" "^7.11.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" - "@babel/plugin-proposal-numeric-separator" "^7.10.4" - "@babel/plugin-proposal-object-rest-spread" "^7.11.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" - "@babel/plugin-proposal-optional-chaining" "^7.11.0" - "@babel/plugin-proposal-private-methods" "^7.10.4" - "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" + "@babel/helper-validator-option" "^7.12.1" + "@babel/plugin-proposal-async-generator-functions" "^7.12.1" + "@babel/plugin-proposal-class-properties" "^7.12.1" + "@babel/plugin-proposal-dynamic-import" "^7.12.1" + "@babel/plugin-proposal-export-namespace-from" "^7.12.1" + "@babel/plugin-proposal-json-strings" "^7.12.1" + "@babel/plugin-proposal-logical-assignment-operators" "^7.12.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" + "@babel/plugin-proposal-numeric-separator" "^7.12.7" + "@babel/plugin-proposal-object-rest-spread" "^7.12.1" + "@babel/plugin-proposal-optional-catch-binding" "^7.12.1" + "@babel/plugin-proposal-optional-chaining" "^7.12.7" + "@babel/plugin-proposal-private-methods" "^7.12.1" + "@babel/plugin-proposal-unicode-property-regex" "^7.12.1" "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-class-properties" "^7.10.4" + "@babel/plugin-syntax-class-properties" "^7.12.1" "@babel/plugin-syntax-dynamic-import" "^7.8.0" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" "@babel/plugin-syntax-json-strings" "^7.8.0" @@ -825,45 +850,42 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.10.4" - "@babel/plugin-transform-arrow-functions" "^7.10.4" - "@babel/plugin-transform-async-to-generator" "^7.10.4" - "@babel/plugin-transform-block-scoped-functions" "^7.10.4" - "@babel/plugin-transform-block-scoping" "^7.10.4" - "@babel/plugin-transform-classes" "^7.10.4" - "@babel/plugin-transform-computed-properties" "^7.10.4" - "@babel/plugin-transform-destructuring" "^7.10.4" - "@babel/plugin-transform-dotall-regex" "^7.10.4" - "@babel/plugin-transform-duplicate-keys" "^7.10.4" - "@babel/plugin-transform-exponentiation-operator" "^7.10.4" - "@babel/plugin-transform-for-of" "^7.10.4" - "@babel/plugin-transform-function-name" "^7.10.4" - "@babel/plugin-transform-literals" "^7.10.4" - "@babel/plugin-transform-member-expression-literals" "^7.10.4" - "@babel/plugin-transform-modules-amd" "^7.10.4" - "@babel/plugin-transform-modules-commonjs" "^7.10.4" - "@babel/plugin-transform-modules-systemjs" "^7.10.4" - "@babel/plugin-transform-modules-umd" "^7.10.4" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" - "@babel/plugin-transform-new-target" "^7.10.4" - "@babel/plugin-transform-object-super" "^7.10.4" - "@babel/plugin-transform-parameters" "^7.10.4" - "@babel/plugin-transform-property-literals" "^7.10.4" - "@babel/plugin-transform-regenerator" "^7.10.4" - "@babel/plugin-transform-reserved-words" "^7.10.4" - "@babel/plugin-transform-shorthand-properties" "^7.10.4" - "@babel/plugin-transform-spread" "^7.11.0" - "@babel/plugin-transform-sticky-regex" "^7.10.4" - "@babel/plugin-transform-template-literals" "^7.10.4" - "@babel/plugin-transform-typeof-symbol" "^7.10.4" - "@babel/plugin-transform-unicode-escapes" "^7.10.4" - "@babel/plugin-transform-unicode-regex" "^7.10.4" + "@babel/plugin-syntax-top-level-await" "^7.12.1" + "@babel/plugin-transform-arrow-functions" "^7.12.1" + "@babel/plugin-transform-async-to-generator" "^7.12.1" + "@babel/plugin-transform-block-scoped-functions" "^7.12.1" + "@babel/plugin-transform-block-scoping" "^7.12.1" + "@babel/plugin-transform-classes" "^7.12.1" + "@babel/plugin-transform-computed-properties" "^7.12.1" + "@babel/plugin-transform-destructuring" "^7.12.1" + "@babel/plugin-transform-dotall-regex" "^7.12.1" + "@babel/plugin-transform-duplicate-keys" "^7.12.1" + "@babel/plugin-transform-exponentiation-operator" "^7.12.1" + "@babel/plugin-transform-for-of" "^7.12.1" + "@babel/plugin-transform-function-name" "^7.12.1" + "@babel/plugin-transform-literals" "^7.12.1" + "@babel/plugin-transform-member-expression-literals" "^7.12.1" + "@babel/plugin-transform-modules-amd" "^7.12.1" + "@babel/plugin-transform-modules-commonjs" "^7.12.1" + "@babel/plugin-transform-modules-systemjs" "^7.12.1" + "@babel/plugin-transform-modules-umd" "^7.12.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.1" + "@babel/plugin-transform-new-target" "^7.12.1" + "@babel/plugin-transform-object-super" "^7.12.1" + "@babel/plugin-transform-parameters" "^7.12.1" + "@babel/plugin-transform-property-literals" "^7.12.1" + "@babel/plugin-transform-regenerator" "^7.12.1" + "@babel/plugin-transform-reserved-words" "^7.12.1" + "@babel/plugin-transform-shorthand-properties" "^7.12.1" + "@babel/plugin-transform-spread" "^7.12.1" + "@babel/plugin-transform-sticky-regex" "^7.12.7" + "@babel/plugin-transform-template-literals" "^7.12.1" + "@babel/plugin-transform-typeof-symbol" "^7.12.1" + "@babel/plugin-transform-unicode-escapes" "^7.12.1" + "@babel/plugin-transform-unicode-regex" "^7.12.1" "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.11.5" - browserslist "^4.12.0" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" + "@babel/types" "^7.12.7" + core-js-compat "^3.7.0" semver "^5.5.0" "@babel/preset-modules@^0.1.3": @@ -877,23 +899,23 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.9.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.4.tgz#92e8a66d816f9911d11d4cc935be67adfc82dbcf" - integrity sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw== +"@babel/preset-react@^7.12.5": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.12.7.tgz#36d61d83223b07b6ac4ec55cf016abb0f70be83b" + integrity sha512-wKeTdnGUP5AEYCYQIMeXMMwU7j+2opxrG0WzuZfxuuW9nhKvvALBjl67653CWamZJVefuJGI219G591RSldrqQ== dependencies: "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-transform-react-display-name" "^7.10.4" - "@babel/plugin-transform-react-jsx" "^7.10.4" - "@babel/plugin-transform-react-jsx-development" "^7.10.4" - "@babel/plugin-transform-react-jsx-self" "^7.10.4" - "@babel/plugin-transform-react-jsx-source" "^7.10.4" - "@babel/plugin-transform-react-pure-annotations" "^7.10.4" + "@babel/plugin-transform-react-display-name" "^7.12.1" + "@babel/plugin-transform-react-jsx" "^7.12.7" + "@babel/plugin-transform-react-jsx-development" "^7.12.7" + "@babel/plugin-transform-react-jsx-self" "^7.12.1" + "@babel/plugin-transform-react-jsx-source" "^7.12.1" + "@babel/plugin-transform-react-pure-annotations" "^7.12.1" -"@babel/register@^7.9.0": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/register/-/register-7.11.5.tgz#79becf89e0ddd0fba8b92bc279bc0f5d2d7ce2ea" - integrity sha512-CAml0ioKX+kOAvBQDHa/+t1fgOt3qkTIz0TrRtRAT6XY0m5qYZXR85k6/sLCNPMGhYDlCFHCYuU0ybTJbvlC6w== +"@babel/register@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.12.1.tgz#cdb087bdfc4f7241c03231f22e15d211acf21438" + integrity sha512-XWcmseMIncOjoydKZnWvWi0/5CUCD+ZYKhRwgYlWOrA8fGZ/FjuLRpqtIhLOVD/fvR1b9DQHtZPn68VvhpYf+Q== dependencies: find-cache-dir "^2.0.0" lodash "^4.17.19" @@ -917,25 +939,34 @@ "@babel/parser" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5", "@babel/traverse@^7.9.0": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" - integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ== +"@babel/template@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc" + integrity sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.5" + "@babel/parser" "^7.12.7" + "@babel/types" "^7.12.7" + +"@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.5", "@babel/traverse@^7.12.9": + version "7.12.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.9.tgz#fad26c972eabbc11350e0b695978de6cc8e8596f" + integrity sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.5" "@babel/helper-function-name" "^7.10.4" "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/parser" "^7.11.5" - "@babel/types" "^7.11.5" + "@babel/parser" "^7.12.7" + "@babel/types" "^7.12.7" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.19" -"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.4.4", "@babel/types@^7.9.0": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" - integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q== +"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.12.5", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.4.4": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13" + integrity sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ== dependencies: "@babel/helper-validator-identifier" "^7.10.4" lodash "^4.17.19" @@ -1174,6 +1205,11 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + atob@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -1404,7 +1440,7 @@ browserslist@4.7.0: electron-to-chromium "^1.3.247" node-releases "^1.1.29" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.5: +browserslist@^4.0.0, browserslist@^4.12.0: version "4.14.1" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.14.1.tgz#cb2b490ba881d45dc3039078c7ed04411eaf3fa3" integrity sha512-zyBTIHydW37pnb63c7fHFXUG6EcqWOqoMdDx6cdyaDFriZ20EoVxcE95S54N+heRqY8m8IUgB5zYta/gCwSaaA== @@ -1414,6 +1450,17 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.5: escalade "^3.0.2" node-releases "^1.1.60" +browserslist@^4.14.5, browserslist@^4.14.7: + version "4.15.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.15.0.tgz#3d48bbca6a3f378e86102ffd017d9a03f122bdb0" + integrity sha512-IJ1iysdMkGmjjYeRlDU8PQejVwxvVO5QOfXH7ylW31GO6LwNRSmm/SgRXtNsEXqMLl2e+2H5eEJ7sfynF8TCaQ== + dependencies: + caniuse-lite "^1.0.30001164" + colorette "^1.2.1" + electron-to-chromium "^1.3.612" + escalade "^3.1.1" + node-releases "^1.1.67" + buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -1540,6 +1587,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30001109, can resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001124.tgz#5d9998190258e11630d674fc50ea8e579ae0ced2" integrity sha512-zQW8V3CdND7GHRH6rxm6s59Ww4g/qGWTheoboW9nfeMg7sUoopIfKCcNZUjwYRCOrvereh3kwDpZj4VLQ7zGtA== +caniuse-lite@^1.0.30001164: + version "1.0.30001164" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001164.tgz#5bbfd64ca605d43132f13cc7fdabb17c3036bfdc" + integrity sha512-G+A/tkf4bu0dSp9+duNiXc7bGds35DioCyC6vgK2m/rjA4Krpy5WeZgZyfH2f0wj2kI6yAWWucyap6oOwmY1mg== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1845,12 +1897,12 @@ copy-descriptor@^0.1.0: resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js-compat@^3.6.2: - version "3.6.5" - resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" - integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== +core-js-compat@^3.7.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.8.0.tgz#3248c6826f4006793bd637db608bca6e4cd688b1" + integrity sha512-o9QKelQSxQMYWHXc/Gc4L8bx/4F7TTraE5rhuN8I7mKBt5dBIUpXpIR3omv70ebr8ST5R3PqbDQr+ZI3+Tt1FQ== dependencies: - browserslist "^4.8.5" + browserslist "^4.14.7" semver "7.0.0" core-js@^2.6.5: @@ -2233,20 +2285,20 @@ dir-glob@2.0.0: arrify "^1.0.1" path-type "^3.0.0" -docusaurus@^2.0.0-alpha.66: - version "2.0.0-alpha.66" - resolved "https://registry.yarnpkg.com/docusaurus/-/docusaurus-2.0.0-alpha.66.tgz#4dece48b838f773c4c973d9fe2546dd0bf2637cf" - integrity sha512-/HmRt3hEqpvZRdR2JRYKpwNUgalO14exxuwQu2vXW5eYzcKbQ922+3SxG7mF+LE8f7KOmgY+A3eS3SEyq+S3XA== +docusaurus@^2.0.0-alpha.378053ac5: + version "2.0.0-alpha.378053ac5" + resolved "https://registry.yarnpkg.com/docusaurus/-/docusaurus-2.0.0-alpha.378053ac5.tgz#9ca31969ef6eb8958692948ae1fd1d4e0f452f44" + integrity sha512-+NM1NrJKYcmHYiMQ/4b5ew9QDbLW3QI/ii2lCs/gpEZhtU0FVnl1RLC3FUO41xgOhth3A8M5G5ChzlPuNzxjug== dependencies: - "@babel/core" "^7.9.0" - "@babel/plugin-proposal-class-properties" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.9.0" - "@babel/polyfill" "^7.8.7" - "@babel/preset-env" "^7.9.0" - "@babel/preset-react" "^7.9.4" - "@babel/register" "^7.9.0" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" + "@babel/core" "^7.12.3" + "@babel/plugin-proposal-class-properties" "^7.12.1" + "@babel/plugin-proposal-object-rest-spread" "^7.12.1" + "@babel/polyfill" "^7.12.1" + "@babel/preset-env" "^7.12.1" + "@babel/preset-react" "^7.12.5" + "@babel/register" "^7.12.1" + "@babel/traverse" "^7.12.5" + "@babel/types" "^7.12.6" autoprefixer "^9.7.5" babylon "^6.18.0" chalk "^3.0.0" @@ -2256,10 +2308,10 @@ docusaurus@^2.0.0-alpha.66: cssnano "^4.1.10" escape-string-regexp "^2.0.0" express "^4.17.1" - feed "^4.0.0" - fs-extra "^8.1.0" + feed "^4.2.1" + fs-extra "^9.0.1" gaze "^1.1.3" - github-slugger "^1.2.1" + github-slugger "^1.3.0" glob "^7.1.6" highlight.js "^9.16.2" imagemin "^6.0.0" @@ -2267,12 +2319,12 @@ docusaurus@^2.0.0-alpha.66: imagemin-jpegtran "^6.0.0" imagemin-optipng "^6.0.0" imagemin-svgo "^7.0.0" - lodash "^4.17.15" + lodash "^4.17.20" markdown-toc "^1.2.0" mkdirp "^0.5.1" - portfinder "^1.0.25" + portfinder "^1.0.28" postcss "^7.0.23" - prismjs "^1.17.1" + prismjs "^1.22.0" react "^16.8.4" react-dev-utils "^9.1.0" react-dom "^16.8.4" @@ -2404,6 +2456,11 @@ electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.562: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.562.tgz#79c20277ee1c8d0173a22af00e38433b752bc70f" integrity sha512-WhRe6liQ2q/w1MZc8mD8INkenHivuHdrr4r5EQHNomy3NJux+incP6M6lDMd0paShP3MD0WGe5R1TWmEClf+Bg== +electron-to-chromium@^1.3.612: + version "1.3.614" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.614.tgz#ff359e8d2249e2ce859a4c2bc34c22bd2e2eb0a2" + integrity sha512-JMDl46mg4G+n6q/hAJkwy9eMTj5FJjsE+8f/irAGRMLM4yeRVbMuRrdZrbbGGOrGVcZc4vJPjUpEUWNb/fA6hg== + "emoji-regex@>=6.0.0 <=6.1.1": version "6.1.1" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" @@ -2481,6 +2538,11 @@ escalade@^3.0.2: resolved "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -2729,9 +2791,9 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -feed@^4.0.0: +feed@^4.2.1: version "4.2.1" - resolved "https://registry.npmjs.org/feed/-/feed-4.2.1.tgz#b246ef891051c7dbf088ca203341d9fb0444baee" + resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.1.tgz#b246ef891051c7dbf088ca203341d9fb0444baee" integrity sha512-l28KKcK1J/u3iq5dRDmmoB2p7dtBfACC2NqJh4dI2kFptxH0asfjmOfcxqh5Sv8suAlVa73gZJ4REY5RrafVvg== dependencies: xml-js "^1.6.11" @@ -2940,14 +3002,15 @@ fs-constants@^1.0.0: resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== +fs-extra@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== dependencies: + at-least-node "^1.0.0" graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" + jsonfile "^6.0.1" + universalify "^1.0.0" fs.realpath@^1.0.0: version "1.0.0" @@ -3033,9 +3096,9 @@ gifsicle@^4.0.0: execa "^1.0.0" logalot "^2.0.0" -github-slugger@^1.2.1: +github-slugger@^1.3.0: version "1.3.0" - resolved "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q== dependencies: emoji-regex ">=6.0.0 <=6.1.1" @@ -3508,13 +3571,6 @@ into-stream@^3.1.0: from2 "^2.1.1" p-is-promise "^1.1.0" -invariant@^2.2.2, invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -3932,10 +3988,12 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" @@ -3987,18 +4045,6 @@ lazy-cache@^2.0.2: dependencies: set-getter "^0.1.0" -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levenary@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" - integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== - dependencies: - leven "^3.1.0" - list-item@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz#0c65d00e287cb663ccb3cb3849a77e89ec268a56" @@ -4155,7 +4201,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@~4.17.10: +lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@~4.17.10: version "4.17.20" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -4173,7 +4219,7 @@ longest@^1.0.0: resolved "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -4462,6 +4508,11 @@ node-releases@^1.1.29, node-releases@^1.1.60: resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== +node-releases@^1.1.67: + version "1.1.67" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.67.tgz#28ebfcccd0baa6aad8e8d4d8fe4cbc49ae239c12" + integrity sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg== + normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.5.0" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -4896,9 +4947,9 @@ pkg-up@2.0.0: dependencies: find-up "^2.1.0" -portfinder@^1.0.25: +portfinder@^1.0.28: version "1.0.28" - resolved "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== dependencies: async "^2.6.2" @@ -5209,10 +5260,10 @@ prettier@^2.2.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== -prismjs@^1.17.1: - version "1.21.0" - resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.21.0.tgz#36c086ec36b45319ec4218ee164c110f9fc015a3" - integrity sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw== +prismjs@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.22.0.tgz#73c3400afc58a823dd7eed023f8e1ce9fd8977fa" + integrity sha512-lLJ/Wt9yy0AiSYBf212kK3mM5L8ycwlyTlSxHBAneXLR0nzFMlZ5y7riFPF3E33zXOF2IH95xdY5jIyZbM9z/w== optionalDependencies: clipboard "^2.0.0" @@ -5506,6 +5557,18 @@ regexpu-core@^4.7.0: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.2.0" +regexpu-core@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" + integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + regjsgen@^0.5.1: version "0.5.2" resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" @@ -6438,10 +6501,15 @@ uniqs@^2.0.0: resolved "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" From 1665ae8bb8c798d05ee402a8dfcf7ff463ee5a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Mon, 7 Dec 2020 10:17:59 +0100 Subject: [PATCH 16/19] Slightly more padding in dense tables (#3571) * Slightly more padding in dense tables * Added changeset * Update small-comics-cover.md --- .changeset/small-comics-cover.md | 5 +++++ packages/theme/src/baseTheme.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/small-comics-cover.md diff --git a/.changeset/small-comics-cover.md b/.changeset/small-comics-cover.md new file mode 100644 index 0000000000..90fa15593d --- /dev/null +++ b/.changeset/small-comics-cover.md @@ -0,0 +1,5 @@ +--- +'@backstage/theme': patch +--- + +Add a little more padding in dense tables diff --git a/packages/theme/src/baseTheme.ts b/packages/theme/src/baseTheme.ts index 193f6961d6..37b8e3b47f 100644 --- a/packages/theme/src/baseTheme.ts +++ b/packages/theme/src/baseTheme.ts @@ -135,7 +135,7 @@ export function createThemeOverrides(theme: BackstageTheme): Overrides { borderBottom: 0, }, sizeSmall: { - padding: theme.spacing(1, 2, 1, 2.5), + padding: theme.spacing(1.5, 2, 1.5, 2.5), }, head: { wordBreak: 'break-word', From 03e264519a3ea82c7fb88278ec2fd1d34fbfd6c6 Mon Sep 17 00:00:00 2001 From: David Tuite Date: Mon, 7 Dec 2020 10:46:40 +0000 Subject: [PATCH 17/19] Fix techdocs for url locations (#3589) Pasting a URL like `https://github.com/RoadieHQ/sample-service/blob/master/catalog-info-1.yaml` into the Register Existing Component input creates an Entity with the the `backstage.io/managed-by-location` annotation set to the `type` of `url. The TechDocs perparer does not handle this type. ```ts switch (type) { case 'github': case 'gitlab': case 'azure/api': { // ... } case 'file': // ... default: throw new InputError(`Unable to resolve location type ${type}`); } } ``` Thus, docs are never generated and we see the following error when trying to view then in Backstage. ``` InputError: Unable to resolve location type url at DirectoryPreparer.resolveManagedByLocationToDir (webpack-internal:///../../node_modules/@backstage/plugin-techdocs-backend/src/techdocs/stages/prepare/dir.ts:63:15) at DirectoryPreparer.prepare (webpack-internal:///../../node_modules/@backstage/plugin-techdocs-backend/src/techdocs/stages/prepare/dir.ts:73:51) at DocsBuilder.build (webpack-internal:///../../node_modules/@backstage/plugin-techdocs-backend/src/service/helpers.ts:74:45) at eval (webpack-internal:///../../node_modules/@backstage/plugin-techdocs-backend/src/service/router.ts:141:25) at processTicksAndRejections (internal/process/task_queues.js:93:5) ``` This may not be the correct solution. An alternative solution would be to ensure that pasted URLs end up with the `type` of `github` rather than `url`. --- plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts index 1effcf61b5..8faca33606 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts @@ -43,6 +43,7 @@ export class DirectoryPreparer implements PreparerBase { switch (type) { case 'github': case 'gitlab': + case 'url': case 'azure/api': { const parsedGitLocation = parseGitUrl(target); const repoLocation = await checkoutGitRepository(target, this.logger); From 040f453c36418f1ba3aacd193f9465b946c3eae8 Mon Sep 17 00:00:00 2001 From: Mateusz Lewtak <36006588+lewtakm@users.noreply.github.com> Date: Mon, 7 Dec 2020 16:46:15 +0100 Subject: [PATCH 18/19] Feat: Org Plugin (#3448) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Create Groups plugin * Feat: update code after CR * Feat: change routing, replace filters to use relations, modify EntityPageLayout to support users * Feat: update CLI version * Feat: add some tests * Update daily cost data to return groupedCosts * Add aggregation sum util * Add top panel breakdown view * Add top panel tabs * Add mock data for grouped Costs * Add comments on groupedCosts * Update wording to product in cost by product component * Move cost overview chart legend to separate component * Move mock data utils * Add data viz colors for both themes * Move mock data utils * Update data viz dark theme colors * Update bar chart legend type usage * catalog-backend: gracefully handle missing codeowners * Derive the owned entities in the catalog from group memberships * Filter the response headers in the proxy backend * Make backend-commons tests work on Windows * backend: remove yarn cache from built docker image * Refactor the hooks and also support users owning components * backend,yarnrc: bring yarn network-timeout down to 300s * Fix another Windows test issue This one is a bit tricky. Instead of testing at the root folder of the drive, this test is now relative to the current directory. This resolves the difference between "/pkgs/a" and "d:\pkgs\a" * Drop fill opacity for lighter colors * Update dark theme colors * Fix review comments * techdocs-backend: update Gitlab clone auth * Use case-insensitive filters * Add Kubernetes plugin (#3505) * backend-common: allow port in config to be both a number or a string * chore: set the port as number if it comes through as a string * Feat: Bump GitHub Insights plugin version (#3509) * Feat: groups and components card * Feat: update Org plugin according to CR * Feat: update TS stuff * Feat: change tile titles * Feat: bump packages * Code review fixes Co-authored-by: Brenda Sukh Co-authored-by: Fredrik Adelöw Co-authored-by: Dominik Henneke Co-authored-by: Oliver Sand Co-authored-by: Patrik Oldsberg Co-authored-by: Chongyang Adrian, Ke Co-authored-by: Adam Harvey Co-authored-by: blam Co-authored-by: Marek Calus --- packages/app/package.json | 1 + .../app/src/components/catalog/EntityPage.tsx | 62 +++++- packages/app/src/plugins.ts | 1 + .../EntityPageLayout/EntityPageLayout.tsx | 9 +- plugins/org/.eslintrc.js | 3 + plugins/org/README.md | 6 + plugins/org/dev/index.tsx | 19 ++ plugins/org/package.json | 50 +++++ plugins/org/src/components/Avatar/Avatar.tsx | 73 +++++++ plugins/org/src/components/Avatar/index.ts | 16 ++ .../Group/GroupProfile/GroupProfileCard.tsx | 141 +++++++++++++ .../Cards/Group/GroupProfile/index.ts | 16 ++ .../MembersList/MembersListCard.test.tsx | 100 +++++++++ .../Group/MembersList/MembersListCard.tsx | 155 ++++++++++++++ .../Cards/Group/MembersList/index.ts | 16 ++ .../org/src/components/Cards/Group/index.ts | 17 ++ .../Cards/OwnershipCard/OwnershipCard.tsx | 196 ++++++++++++++++++ .../components/Cards/OwnershipCard/index.ts | 16 ++ .../UserProfileCard/UserProfileCard.test.tsx | 64 ++++++ .../User/UserProfileCard/UserProfileCard.tsx | 134 ++++++++++++ .../Cards/User/UserProfileCard/index.ts | 16 ++ .../org/src/components/Cards/User/index.ts | 16 ++ plugins/org/src/components/Cards/index.ts | 18 ++ plugins/org/src/components/index.ts | 17 ++ plugins/org/src/index.ts | 17 ++ plugins/org/src/plugin.test.ts | 22 ++ plugins/org/src/plugin.ts | 20 ++ plugins/org/src/setupTests.ts | 17 ++ 28 files changed, 1233 insertions(+), 5 deletions(-) create mode 100644 plugins/org/.eslintrc.js create mode 100644 plugins/org/README.md create mode 100644 plugins/org/dev/index.tsx create mode 100644 plugins/org/package.json create mode 100644 plugins/org/src/components/Avatar/Avatar.tsx create mode 100644 plugins/org/src/components/Avatar/index.ts create mode 100644 plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx create mode 100644 plugins/org/src/components/Cards/Group/GroupProfile/index.ts create mode 100644 plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx create mode 100644 plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx create mode 100644 plugins/org/src/components/Cards/Group/MembersList/index.ts create mode 100644 plugins/org/src/components/Cards/Group/index.ts create mode 100644 plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx create mode 100644 plugins/org/src/components/Cards/OwnershipCard/index.ts create mode 100644 plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx create mode 100644 plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx create mode 100644 plugins/org/src/components/Cards/User/UserProfileCard/index.ts create mode 100644 plugins/org/src/components/Cards/User/index.ts create mode 100644 plugins/org/src/components/Cards/index.ts create mode 100644 plugins/org/src/components/index.ts create mode 100644 plugins/org/src/index.ts create mode 100644 plugins/org/src/plugin.test.ts create mode 100644 plugins/org/src/plugin.ts create mode 100644 plugins/org/src/setupTests.ts diff --git a/packages/app/package.json b/packages/app/package.json index e671e23b79..d46ce8b439 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -18,6 +18,7 @@ "@backstage/plugin-github-actions": "^0.2.3", "@backstage/plugin-gitops-profiles": "^0.2.1", "@backstage/plugin-graphiql": "^0.2.1", + "@backstage/plugin-org": "^0.3.0", "@backstage/plugin-jenkins": "^0.3.2", "@backstage/plugin-kubernetes": "^0.3.1", "@backstage/plugin-lighthouse": "^0.2.4", diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 13cbe324c6..2378afbc5e 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiEntity, Entity } from '@backstage/catalog-model'; +import { + ApiEntity, + Entity, + GroupEntity, + UserEntity, +} from '@backstage/catalog-model'; import { EmptyState } from '@backstage/core'; import { ApiDefinitionCard, @@ -48,6 +53,12 @@ import { isPluginApplicableToEntity as isLighthouseAvailable, LastLighthouseAuditCard, } from '@backstage/plugin-lighthouse'; +import { + OwnershipCard, + MembersListCard, + GroupProfileCard, + UserProfileCard, +} from '@backstage/plugin-org'; import { Router as SentryRouter } from '@backstage/plugin-sentry'; import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; import { Button, Grid } from '@material-ui/core'; @@ -323,6 +334,51 @@ const ApiEntityPage = ({ entity }: { entity: Entity }) => ( ); +const UserOverviewContent = ({ entity }: { entity: UserEntity }) => ( + + + + + + + + +); + +const UserEntityPage = ({ entity }: { entity: Entity }) => ( + + } + /> + +); + +const GroupOverviewContent = ({ entity }: { entity: GroupEntity }) => ( + + + + + + + + + + + +); + +const GroupEntityPage = ({ entity }: { entity: Entity }) => ( + + } + /> + +); + export const EntityPage = () => { const { entity } = useEntity(); @@ -331,6 +387,10 @@ export const EntityPage = () => { return ; case 'api': return ; + case 'group': + return ; + case 'user': + return ; default: return ; } diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index c6a2a0e7e0..6a4924cb0e 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -42,3 +42,4 @@ export { plugin as UserSettings } from '@backstage/plugin-user-settings'; export { plugin as PagerDuty } from '@backstage/plugin-pagerduty'; export { plugin as Buildkite } from '@roadiehq/backstage-plugin-buildkite'; export { plugin as Search } from '@backstage/plugin-search'; +export { plugin as Org } from '@backstage/plugin-org'; diff --git a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx index 8c48d69413..eb75593ffc 100644 --- a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx +++ b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx @@ -39,12 +39,12 @@ const EntityPageTitle = ({ ); -function headerProps( +const headerProps = ( kind: string, namespace: string | undefined, name: string, entity: Entity | undefined, -): { headerTitle: string; headerType: string } { +): { headerTitle: string; headerType: string } => { return { headerTitle: `${name}${ namespace && namespace !== ENTITY_DEFAULT_NAMESPACE @@ -60,7 +60,7 @@ function headerProps( return t; })(), }; -} +}; export const EntityPageLayout = ({ children }: PropsWithChildren<{}>) => { const { kind, namespace, name } = useEntityCompoundName(); @@ -88,7 +88,8 @@ export const EntityPageLayout = ({ children }: PropsWithChildren<{}>) => { pageTitleOverride={headerTitle} type={headerType} > - {entity && ( + {/* TODO: fix after catalog page customization is added */} + {entity && kind !== 'user' && ( <> + createStyles({ + avatar: { + width: '4rem', + height: '4rem', + color: '#fff', + fontWeight: theme.typography.fontWeightBold, + letterSpacing: '1px', + textTransform: 'uppercase', + }, + }), +); + +const stringToColour = (str: string) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + let colour = '#'; + for (let i = 0; i < 3; i++) { + const value = (hash >> (i * 8)) & 0xff; + colour += `00${value.toString(16)}`.substr(-2); + } + return colour; +}; + +export const Avatar = ({ + displayName, + picture, + customStyles, +}: { + displayName: string | undefined; + picture: string | undefined; + customStyles?: CSSProperties; +}) => { + const classes = useStyles(); + return ( + + {displayName && displayName.match(/\b\w/g)!.join('').substring(0, 2)} + + ); +}; diff --git a/plugins/org/src/components/Avatar/index.ts b/plugins/org/src/components/Avatar/index.ts new file mode 100644 index 0000000000..962414634e --- /dev/null +++ b/plugins/org/src/components/Avatar/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +export { Avatar } from './Avatar'; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx new file mode 100644 index 0000000000..ce8829439c --- /dev/null +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -0,0 +1,141 @@ +/* + * 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. + */ + +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { Box, Grid, Link, Tooltip, Typography } from '@material-ui/core'; +import Alert from '@material-ui/lab/Alert'; +import { InfoCard } from '@backstage/core'; +import { entityRouteParams } from '@backstage/plugin-catalog'; +import { + Entity, + GroupEntity, + RELATION_CHILD_OF, + RELATION_PARENT_OF, +} from '@backstage/catalog-model'; +import AccountTreeIcon from '@material-ui/icons/AccountTree'; +import GroupIcon from '@material-ui/icons/Group'; +import { Link as RouterLink, generatePath } from 'react-router-dom'; + +const GroupLink = ({ + groupName, + index = 0, + entity, +}: { + groupName: string; + index?: number; + entity: Entity; +}) => ( + <> + {index >= 1 ? ', ' : ''} + + [{groupName}] + + +); + +const CardTitle = ({ title }: { title: string }) => ( + + + {title} + +); + +export const GroupProfileCard = ({ + entity: group, + variant, +}: { + entity: GroupEntity; + variant: string; +}) => { + const { + metadata: { name, description }, + } = group; + const parent = group?.relations + ?.filter(r => r.type === RELATION_CHILD_OF) + ?.map(group => group.target.name) + .toString(); + + const childrens = group?.relations + ?.filter(r => r.type === RELATION_PARENT_OF) + ?.map(group => group.target.name); + + if (!group) return User not found; + + return ( + } + subheader={description} + variant={variant} + > + + + {parent ? ( + + + + + + + + + + + ) : null} + {childrens?.length ? ( + + + + + + + {childrens.map((children, index) => ( + + ))} + + + + ) : null} + + + + ); +}; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/index.ts b/plugins/org/src/components/Cards/Group/GroupProfile/index.ts new file mode 100644 index 0000000000..44efe25a50 --- /dev/null +++ b/plugins/org/src/components/Cards/Group/GroupProfile/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +export * from './GroupProfileCard'; diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx new file mode 100644 index 0000000000..0c188afc57 --- /dev/null +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx @@ -0,0 +1,100 @@ +/* + * 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 { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; +import React from 'react'; +import { ApiProvider, ApiRegistry } from '@backstage/core'; +import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog'; +import { Entity } from '@backstage/catalog-model'; +import { MembersListCard } from './MembersListCard'; + +describe('MemberTab Test', () => { + const groupEntity = { + apiVersion: 'v1', + kind: 'Group', + metadata: { + name: 'team-d', + description: 'The evil-corp organization', + namespace: 'default', + }, + spec: { + type: 'team', + parent: 'boxoffice', + ancestors: ['boxoffice', 'acme-corp'], + children: [], + descendants: [], + }, + }; + + const catalogApi: Partial = { + getEntities: () => + Promise.resolve({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'tara.macgovern', + namespace: 'default', + uid: 'a5gerth56', + }, + relations: [ + { + type: 'memberOf', + target: { + kind: 'group', + name: 'team-d', + namespace: 'default', + }, + }, + ], + spec: { + profile: { + displayName: 'Tara MacGovern', + email: 'tara-macgovern@example.com', + picture: 'https://example.com/staff/tara.jpeg', + }, + memberOf: ['team-d'], + }, + }, + ] as Entity[], + }), + }; + + const apis = ApiRegistry.from([[catalogApiRef, catalogApi]]); + + it('Display Profile Card', async () => { + const rendered = await renderWithEffects( + wrapInTestApp( + + + , + ), + ); + + expect(rendered.getByAltText('Tara MacGovern')).toHaveAttribute( + 'src', + 'https://example.com/staff/tara.jpeg', + ); + expect( + rendered.getByText('tara-macgovern@example.com'), + ).toBeInTheDocument(); + expect(rendered.getByText('Tara MacGovern')).toHaveAttribute( + 'href', + '/catalog/default/user/tara.macgovern', + ); + }); +}); diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx new file mode 100644 index 0000000000..cd39241ca8 --- /dev/null +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -0,0 +1,155 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import Alert from '@material-ui/lab/Alert'; +import { + Box, + createStyles, + Grid, + Link, + makeStyles, + Theme, + Typography, +} from '@material-ui/core'; +import { InfoCard, Progress, useApi } from '@backstage/core'; +import { + UserEntity, + RELATION_MEMBER_OF, + Entity, +} from '@backstage/catalog-model'; +import { Link as RouterLink, generatePath } from 'react-router-dom'; +import { catalogApiRef, entityRouteParams } from '@backstage/plugin-catalog'; +import { useAsync } from 'react-use'; +import { Avatar } from '../../../Avatar'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + card: { + border: `1px solid ${theme.palette.divider}`, + boxShadow: theme.shadows[2], + borderRadius: '4px', + overflow: 'visible', + position: 'relative', + margin: theme.spacing(3, 0, 0), + }, + }), +); + +const MemberComponent = ({ + member, + groupEntity, +}: { + member: UserEntity; + groupEntity: Entity; +}) => { + const classes = useStyles(); + const { name: metaName } = member.metadata; + const { profile } = member.spec; + return ( + + + + + + + + {profile?.displayName} + + + {profile?.email} + + + + + ); +}; + +export const MembersListCard = ({ + entity: groupEntity, +}: { + entity: Entity; +}) => { + const { + metadata: { name: groupName }, + } = groupEntity; + const catalogApi = useApi(catalogApiRef); + + const { loading, error, value: members } = useAsync(async () => { + const membersList = await catalogApi.getEntities({ + filter: { + kind: 'User', + }, + }); + const groupMembersList = ((membersList.items as unknown) as Array< + UserEntity + >).filter(member => + member?.relations?.some( + r => r.type === RELATION_MEMBER_OF && r.target.name === groupName, + ), + ); + return groupMembersList; + }, [catalogApi]); + + if (loading) { + return ; + } else if (error) { + return {error.message}; + } + + return ( + + + + {members && members.length ? ( + members.map(member => ( + + )) + ) : ( + + This group has no members. + + )} + + + + ); +}; diff --git a/plugins/org/src/components/Cards/Group/MembersList/index.ts b/plugins/org/src/components/Cards/Group/MembersList/index.ts new file mode 100644 index 0000000000..c3f4ea9178 --- /dev/null +++ b/plugins/org/src/components/Cards/Group/MembersList/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +export * from './MembersListCard'; diff --git a/plugins/org/src/components/Cards/Group/index.ts b/plugins/org/src/components/Cards/Group/index.ts new file mode 100644 index 0000000000..a011891f62 --- /dev/null +++ b/plugins/org/src/components/Cards/Group/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ +export * from './MembersList'; +export * from './GroupProfile'; diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx new file mode 100644 index 0000000000..7c4ad7a56a --- /dev/null +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -0,0 +1,196 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { InfoCard, useApi, Progress } from '@backstage/core'; +import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { catalogApiRef } from '@backstage/plugin-catalog'; +import { useAsync } from 'react-use'; +import Alert from '@material-ui/lab/Alert'; +import { + Box, + createStyles, + Grid, + makeStyles, + Theme, + Typography, +} from '@material-ui/core'; +import { pageTheme } from '@backstage/theme'; + +type EntitiesKinds = 'Component' | 'API'; +type EntitiesTypes = + | 'service' + | 'website' + | 'library' + | 'documentation' + | 'api' + | 'tool'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + card: { + border: `1px solid ${theme.palette.divider}`, + boxShadow: theme.shadows[2], + borderRadius: '4px', + padding: theme.spacing(2), + color: '#fff', + transition: `${theme.transitions.duration.standard}ms`, + '&:hover': { + boxShadow: theme.shadows[4], + }, + }, + bold: { + fontWeight: theme.typography.fontWeightBold, + }, + service: { + background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.service.colors})`, + }, + website: { + background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.website.colors})`, + }, + library: { + background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.library.colors})`, + }, + documentation: { + background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.documentation.colors})`, + }, + api: { + background: `${pageTheme.home.shape}, linear-gradient(90deg, #005B4B, #005B4B)`, + }, + tool: { + background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.tool.colors})`, + }, + }), +); + +const countEntitiesBy = ( + entities: Array, + kind: EntitiesKinds, + type?: EntitiesTypes, +) => + entities.filter( + e => e.kind === kind && (type ? e?.spec?.type === type : true), + ).length; + +const EntityCountTile = ({ + counter, + className, + name, +}: { + counter: number; + className: EntitiesTypes; + name: string; +}) => { + const classes = useStyles(); + return ( + + + {counter} + + + {name} + + + ); +}; + +export const OwnershipCard = ({ + entity, + variant, +}: { + entity: Entity; + variant: string; +}) => { + const { + metadata: { name: groupName }, + } = entity; + const catalogApi = useApi(catalogApiRef); + const { + loading, + error, + value: componentsWithCounters, + } = useAsync(async () => { + const entitiesList = await catalogApi.getEntities(); + const ownedEntitiesList = entitiesList.items.filter(component => + component?.relations?.some( + r => r.type === RELATION_OWNED_BY && r.target.name === groupName, + ), + ) as Array; + + return [ + { + counter: countEntitiesBy(ownedEntitiesList, 'Component', 'service'), + className: 'service', + name: 'Services', + }, + { + counter: countEntitiesBy( + ownedEntitiesList, + 'Component', + 'documentation', + ), + className: 'documentation', + name: 'Documentation', + }, + { + counter: countEntitiesBy(ownedEntitiesList, 'API'), + className: 'api', + name: 'APIs', + }, + { + counter: countEntitiesBy(ownedEntitiesList, 'Component', 'library'), + className: 'library', + name: 'Libraries', + }, + { + counter: countEntitiesBy(ownedEntitiesList, 'Component', 'website'), + className: 'website', + name: 'Websites', + }, + { + counter: countEntitiesBy(ownedEntitiesList, 'Component', 'tool'), + className: 'tool', + name: 'Tools', + }, + ] as Array<{ counter: number; className: EntitiesTypes; name: string }>; + }, [catalogApi]); + + if (loading) { + return ; + } else if (error) { + return {error.message}; + } + + return ( + + + {componentsWithCounters?.map(c => ( + + + + ))} + + + ); +}; diff --git a/plugins/org/src/components/Cards/OwnershipCard/index.ts b/plugins/org/src/components/Cards/OwnershipCard/index.ts new file mode 100644 index 0000000000..1fa1bb4044 --- /dev/null +++ b/plugins/org/src/components/Cards/OwnershipCard/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +export * from './OwnershipCard'; diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx new file mode 100644 index 0000000000..77708e6f27 --- /dev/null +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -0,0 +1,64 @@ +/* + * 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 { UserEntity } from '@backstage/catalog-model'; +import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; +import React from 'react'; +import { UserProfileCard } from './UserProfileCard'; + +describe('UserSummary Test', () => { + const userEntity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'calum.leavy', + }, + spec: { + profile: { + displayName: 'Calum Leavy', + email: 'calum-leavy@example.com', + picture: 'https://example.com/staff/calum.jpeg', + }, + memberOf: ['ExampleGroup'], + }, + relations: [ + { + type: 'memberOf', + target: { + kind: 'group', + name: 'ExampleGroup', + namespace: 'default', + }, + }, + ], + }; + + it('Display Profile Card', async () => { + const rendered = await renderWithEffects( + wrapInTestApp(), + ); + + expect(rendered.getByText('calum-leavy@example.com')).toBeInTheDocument(); + expect(rendered.getByAltText('Calum Leavy')).toHaveAttribute( + 'src', + 'https://example.com/staff/calum.jpeg', + ); + expect(rendered.getByText('[ExampleGroup]')).toHaveAttribute( + 'href', + '/catalog/default/group/ExampleGroup', + ); + }); +}); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx new file mode 100644 index 0000000000..0f32835d70 --- /dev/null +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -0,0 +1,134 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { Box, Grid, Link, Tooltip, Typography } from '@material-ui/core'; +import Alert from '@material-ui/lab/Alert'; +import { InfoCard } from '@backstage/core'; +import { entityRouteParams } from '@backstage/plugin-catalog'; +import { + Entity, + RELATION_MEMBER_OF, + UserEntity, +} from '@backstage/catalog-model'; +import EmailIcon from '@material-ui/icons/Email'; +import GroupIcon from '@material-ui/icons/Group'; +import PersonIcon from '@material-ui/icons/Person'; +import { Link as RouterLink, generatePath } from 'react-router-dom'; +import { Avatar } from '../../../Avatar'; + +const GroupLink = ({ + groupName, + index, + entity, +}: { + groupName: string; + index: number; + entity: Entity; +}) => ( + <> + {index >= 1 ? ', ' : ''} + + [{groupName}] + + +); + +const CardTitle = ({ title }: { title?: string }) => + title ? ( + + + {title} + + ) : null; + +export const UserProfileCard = ({ + entity: user, + variant, +}: { + entity: UserEntity; + variant: string; +}) => { + const { + spec: { profile }, + } = user; + const groupNames = + user?.relations + ?.filter(r => r.type === RELATION_MEMBER_OF) + ?.map(group => group.target.name) || []; + + if (!user) return User not found; + + return ( + } + variant={variant} + > + + + + + + + + + + + + + {profile?.email && ( + + {profile.email} + + )} + + + + + + + + + {groupNames.map((groupName, index) => ( + + ))} + + + + + + + ); +}; diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/index.ts b/plugins/org/src/components/Cards/User/UserProfileCard/index.ts new file mode 100644 index 0000000000..dc5e2902b7 --- /dev/null +++ b/plugins/org/src/components/Cards/User/UserProfileCard/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +export * from './UserProfileCard'; diff --git a/plugins/org/src/components/Cards/User/index.ts b/plugins/org/src/components/Cards/User/index.ts new file mode 100644 index 0000000000..dc5e2902b7 --- /dev/null +++ b/plugins/org/src/components/Cards/User/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +export * from './UserProfileCard'; diff --git a/plugins/org/src/components/Cards/index.ts b/plugins/org/src/components/Cards/index.ts new file mode 100644 index 0000000000..62f63da3d4 --- /dev/null +++ b/plugins/org/src/components/Cards/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ +export * from './Group'; +export * from './User'; +export * from './OwnershipCard'; diff --git a/plugins/org/src/components/index.ts b/plugins/org/src/components/index.ts new file mode 100644 index 0000000000..975f66bd25 --- /dev/null +++ b/plugins/org/src/components/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export * from './Cards'; diff --git a/plugins/org/src/index.ts b/plugins/org/src/index.ts new file mode 100644 index 0000000000..77ad7f9266 --- /dev/null +++ b/plugins/org/src/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ +export { plugin } from './plugin'; +export * from './components'; diff --git a/plugins/org/src/plugin.test.ts b/plugins/org/src/plugin.test.ts new file mode 100644 index 0000000000..d77cfd7ae8 --- /dev/null +++ b/plugins/org/src/plugin.test.ts @@ -0,0 +1,22 @@ +/* + * 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 { plugin } from './plugin'; + +describe('groups', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/plugins/org/src/plugin.ts b/plugins/org/src/plugin.ts new file mode 100644 index 0000000000..39c3502fb5 --- /dev/null +++ b/plugins/org/src/plugin.ts @@ -0,0 +1,20 @@ +/* + * 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 { createPlugin } from '@backstage/core'; + +export const plugin = createPlugin({ + id: 'org', +}); diff --git a/plugins/org/src/setupTests.ts b/plugins/org/src/setupTests.ts new file mode 100644 index 0000000000..43b8421558 --- /dev/null +++ b/plugins/org/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * 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 '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; From 87a33d2fe8060407d832e51ce2c595aa8d6f7dad Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Mon, 7 Dec 2020 16:49:24 +0100 Subject: [PATCH 19/19] [TechDocs] Bug fixes and cleanups (#3599) * Fixed issue with some internal doc links reloading the page and removed modifyCss transformer * Create silly-kiwis-rest.md * Formatting in changeset --- .changeset/silly-kiwis-rest.md | 6 ++ .../techdocs/src/reader/components/Reader.tsx | 15 ++--- .../transformers/addLinkClickListener.ts | 6 +- .../techdocs/src/reader/transformers/index.ts | 1 - .../reader/transformers/modifyCss.test.tsx | 58 ------------------- .../src/reader/transformers/modifyCss.ts | 43 -------------- 6 files changed, 14 insertions(+), 115 deletions(-) create mode 100644 .changeset/silly-kiwis-rest.md delete mode 100644 plugins/techdocs/src/reader/transformers/modifyCss.test.tsx delete mode 100644 plugins/techdocs/src/reader/transformers/modifyCss.ts diff --git a/.changeset/silly-kiwis-rest.md b/.changeset/silly-kiwis-rest.md new file mode 100644 index 0000000000..acb0d79bb2 --- /dev/null +++ b/.changeset/silly-kiwis-rest.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +Removed modifyCss transformer and moved the css to injectCss transformer +Fixed issue where some internal doc links would cause a reload of the page diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index bdf91936ba..fb49b90096 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -30,7 +30,6 @@ import transformer, { addLinkClickListener, removeMkdocsHeader, simplifyMkdocsFooter, - modifyCss, onCssReady, sanitizeDOM, injectCss, @@ -71,15 +70,6 @@ export const Reader = ({ entityId, onReady }: Props) => { path, }), rewriteDocLinks(), - modifyCss({ - cssTransforms: { - '.md-main__inner': [{ 'margin-top': '0' }], - '.md-sidebar': [{ top: '0' }, { width: '20rem' }], - '.md-typeset': [{ 'font-size': '1rem' }], - '.md-nav': [{ 'font-size': '1rem' }], - '.md-grid': [{ 'max-width': '80vw' }], - }, - }), removeMkdocsHeader(), simplifyMkdocsFooter(), injectCss({ @@ -92,6 +82,11 @@ export const Reader = ({ entityId, onReady }: Props) => { --md-code-fg-color: ${theme.palette.text.primary}; --md-code-bg-color: ${theme.palette.background.paper}; } + .md-main__inner { margin-top: 0; } + .md-sidebar { top: 0; width: 20rem; } + .md-typeset { font-size: 1rem; } + .md-nav { font-size: 1rem; } + .md-grid { max-width: 80vw; } `, }), ]); diff --git a/plugins/techdocs/src/reader/transformers/addLinkClickListener.ts b/plugins/techdocs/src/reader/transformers/addLinkClickListener.ts index b6a6509550..7c5fd4d9f4 100644 --- a/plugins/techdocs/src/reader/transformers/addLinkClickListener.ts +++ b/plugins/techdocs/src/reader/transformers/addLinkClickListener.ts @@ -28,13 +28,13 @@ export const addLinkClickListener = ({ return dom => { Array.from(dom.getElementsByTagName('a')).forEach(elem => { elem.addEventListener('click', (e: MouseEvent) => { - const target = e.target as HTMLAnchorElement; - const href = target?.getAttribute('href'); + const target = elem as HTMLAnchorElement; + const href = target.getAttribute('href'); if (!href) return; if (href.startsWith(baseUrl)) { e.preventDefault(); - onClick(e, target.getAttribute('href')!); + onClick(e, href); } }); }); diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index 0bab085abe..24f3976d75 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -19,7 +19,6 @@ export * from './rewriteDocLinks'; export * from './addLinkClickListener'; export * from './removeMkdocsHeader'; export * from './simplifyMkdocsFooter'; -export * from './modifyCss'; export * from './onCssReady'; export * from './sanitizeDOM'; export * from './injectCss'; diff --git a/plugins/techdocs/src/reader/transformers/modifyCss.test.tsx b/plugins/techdocs/src/reader/transformers/modifyCss.test.tsx deleted file mode 100644 index fff3869c96..0000000000 --- a/plugins/techdocs/src/reader/transformers/modifyCss.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createTestShadowDom } from '../../test-utils'; -import { modifyCss } from '../transformers'; - -describe('modifyCss', () => { - it('does not modify css', () => { - const shadowDom = createTestShadowDom( - `
`, - { - preTransformers: [], - postTransformers: [], - }, - ); - - const { fontSize } = getComputedStyle( - shadowDom.querySelector('.md-typeset')!, - ); - - expect(fontSize).toBe('0.8em'); - }); - - it('does modify css', () => { - const shadowDom = createTestShadowDom( - `
`, - { - preTransformers: [ - modifyCss({ - cssTransforms: { - '.md-typeset': [{ 'font-size': '1em' }], - }, - }), - ], - postTransformers: [], - }, - ); - - const { fontSize } = getComputedStyle( - shadowDom.querySelector('.md-typeset')!, - ); - - expect(fontSize).toBe('1em'); - }); -}); diff --git a/plugins/techdocs/src/reader/transformers/modifyCss.ts b/plugins/techdocs/src/reader/transformers/modifyCss.ts deleted file mode 100644 index 5116baac45..0000000000 --- a/plugins/techdocs/src/reader/transformers/modifyCss.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Transformer } from './index'; - -type ModifyCssOptions = { - // Example: { '.md-container': { 'marginTop': '10px' }} - cssTransforms: { [key: string]: { [key: string]: string }[] }; -}; - -export const modifyCss = ({ cssTransforms }: ModifyCssOptions): Transformer => { - return dom => { - Object.entries(cssTransforms).forEach(([cssSelector, cssChanges]) => { - const elementsToChange = Array.from( - dom.querySelectorAll(cssSelector), - ); - if (elementsToChange.length < 1) return; - - cssChanges.forEach(changes => { - elementsToChange.forEach((element: HTMLElement) => { - Object.entries(changes).forEach(([cssProperty, cssValue]) => { - element.style.setProperty(cssProperty, cssValue); - }); - }); - }); - }); - - return dom; - }; -};