From 6efab40e059f8a3fae5dcd4d057672930c465410 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 27 Jul 2020 15:36:28 +0200 Subject: [PATCH 01/34] feat(gql): added some experimentation around graphql modules --- plugins/bogus-graphql/.eslintrc.js | 3 + plugins/bogus-graphql/README.md | 13 +++ plugins/bogus-graphql/package.json | 44 ++++++++++ plugins/bogus-graphql/scripts/mock-data.sh | 2 + plugins/bogus-graphql/src/graphql/module.ts | 47 +++++++++++ plugins/bogus-graphql/src/index.ts | 19 +++++ plugins/bogus-graphql/src/schema.gql | 3 + plugins/bogus-graphql/src/setupTests.ts | 18 ++++ plugins/catalog-graphql/.eslintrc.js | 3 + plugins/catalog-graphql/README.md | 13 +++ plugins/catalog-graphql/package.json | 44 ++++++++++ plugins/catalog-graphql/scripts/mock-data.sh | 2 + plugins/catalog-graphql/src/graphql/module.ts | 82 +++++++++++++++++++ plugins/catalog-graphql/src/index.ts | 19 +++++ plugins/catalog-graphql/src/schema.gql | 25 ++++++ plugins/catalog-graphql/src/setupTests.ts | 18 ++++ plugins/graphql/package.json | 3 + plugins/graphql/schema.gql | 11 --- plugins/graphql/src/service/router.ts | 12 ++- 19 files changed, 369 insertions(+), 12 deletions(-) create mode 100644 plugins/bogus-graphql/.eslintrc.js create mode 100644 plugins/bogus-graphql/README.md create mode 100644 plugins/bogus-graphql/package.json create mode 100755 plugins/bogus-graphql/scripts/mock-data.sh create mode 100644 plugins/bogus-graphql/src/graphql/module.ts create mode 100644 plugins/bogus-graphql/src/index.ts create mode 100644 plugins/bogus-graphql/src/schema.gql create mode 100644 plugins/bogus-graphql/src/setupTests.ts create mode 100644 plugins/catalog-graphql/.eslintrc.js create mode 100644 plugins/catalog-graphql/README.md create mode 100644 plugins/catalog-graphql/package.json create mode 100755 plugins/catalog-graphql/scripts/mock-data.sh create mode 100644 plugins/catalog-graphql/src/graphql/module.ts create mode 100644 plugins/catalog-graphql/src/index.ts create mode 100644 plugins/catalog-graphql/src/schema.gql create mode 100644 plugins/catalog-graphql/src/setupTests.ts diff --git a/plugins/bogus-graphql/.eslintrc.js b/plugins/bogus-graphql/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/bogus-graphql/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/bogus-graphql/README.md b/plugins/bogus-graphql/README.md new file mode 100644 index 0000000000..3f85ec766e --- /dev/null +++ b/plugins/bogus-graphql/README.md @@ -0,0 +1,13 @@ +# graphql + +Welcome to the graphql backend plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/graphql](http://localhost:3000/graphql). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. diff --git a/plugins/bogus-graphql/package.json b/plugins/bogus-graphql/package.json new file mode 100644 index 0000000000..a5a439a701 --- /dev/null +++ b/plugins/bogus-graphql/package.json @@ -0,0 +1,44 @@ +{ + "name": "@backstage/plugin-bogus-graphql", + "version": "0.1.1-alpha.16", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.16", + "@graphql-modules/core": "^0.7.17", + "@types/express": "^4.17.6", + "@types/whatwg-fetch": "^0.0.33", + "graphql": "^15.3.0", + "node-fetch": "^2.6.0", + "whatwg-fetch": "^2.0.0", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.16", + "@types/supertest": "^2.0.8", + "eslint-plugin-graphql": "^4.0.0", + "msw": "^0.19.5", + "supertest": "^4.0.2" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/bogus-graphql/scripts/mock-data.sh b/plugins/bogus-graphql/scripts/mock-data.sh new file mode 100755 index 0000000000..ff30921715 --- /dev/null +++ b/plugins/bogus-graphql/scripts/mock-data.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +echo "use this script to load your service with some mock data if needed!" diff --git a/plugins/bogus-graphql/src/graphql/module.ts b/plugins/bogus-graphql/src/graphql/module.ts new file mode 100644 index 0000000000..f247686ea8 --- /dev/null +++ b/plugins/bogus-graphql/src/graphql/module.ts @@ -0,0 +1,47 @@ +/* + * 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 { Logger } from 'winston'; +import fs from 'fs'; +import path from 'path'; +import { GraphQLModule } from '@graphql-modules/core'; + +export interface ModuleOptions { + logger: Logger; +} + +export async function createModule( + options: ModuleOptions, +): Promise { + const typeDefs = await fs.promises.readFile( + path.resolve(__dirname, '..', 'schema.gql'), + 'utf-8', + ); + + const resolvers = { + EntityMetadata: { + uppercaseName: metadata => metadata.name.toUpperCase(), + }, + }; + + const module = new GraphQLModule({ + typeDefs, + resolvers, + logger: options.logger as any, + }); + + return module; +} diff --git a/plugins/bogus-graphql/src/index.ts b/plugins/bogus-graphql/src/index.ts new file mode 100644 index 0000000000..dd6508f88a --- /dev/null +++ b/plugins/bogus-graphql/src/index.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +require('whatwg-fetch'); + +export * from './graphql/module'; diff --git a/plugins/bogus-graphql/src/schema.gql b/plugins/bogus-graphql/src/schema.gql new file mode 100644 index 0000000000..74c0853a92 --- /dev/null +++ b/plugins/bogus-graphql/src/schema.gql @@ -0,0 +1,3 @@ +type EntityMetadata { + uppercaseName: String +} diff --git a/plugins/bogus-graphql/src/setupTests.ts b/plugins/bogus-graphql/src/setupTests.ts new file mode 100644 index 0000000000..ac41ef995a --- /dev/null +++ b/plugins/bogus-graphql/src/setupTests.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. + */ +require('whatwg-fetch'); + +export {}; diff --git a/plugins/catalog-graphql/.eslintrc.js b/plugins/catalog-graphql/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/catalog-graphql/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/catalog-graphql/README.md b/plugins/catalog-graphql/README.md new file mode 100644 index 0000000000..3f85ec766e --- /dev/null +++ b/plugins/catalog-graphql/README.md @@ -0,0 +1,13 @@ +# graphql + +Welcome to the graphql backend plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/graphql](http://localhost:3000/graphql). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json new file mode 100644 index 0000000000..57fade9654 --- /dev/null +++ b/plugins/catalog-graphql/package.json @@ -0,0 +1,44 @@ +{ + "name": "@backstage/plugin-catalog-graphql", + "version": "0.1.1-alpha.16", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.16", + "@graphql-modules/core": "^0.7.17", + "@types/express": "^4.17.6", + "@types/whatwg-fetch": "^0.0.33", + "graphql": "^15.3.0", + "node-fetch": "^2.6.0", + "whatwg-fetch": "^2.0.0", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.16", + "@types/supertest": "^2.0.8", + "eslint-plugin-graphql": "^4.0.0", + "msw": "^0.19.5", + "supertest": "^4.0.2" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/catalog-graphql/scripts/mock-data.sh b/plugins/catalog-graphql/scripts/mock-data.sh new file mode 100755 index 0000000000..ff30921715 --- /dev/null +++ b/plugins/catalog-graphql/scripts/mock-data.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +echo "use this script to load your service with some mock data if needed!" diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts new file mode 100644 index 0000000000..65239d27db --- /dev/null +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -0,0 +1,82 @@ +/* + * 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 { Logger } from 'winston'; +import fetch from 'node-fetch'; +import fs from 'fs'; +import path from 'path'; +import { GraphQLModule } from '@graphql-modules/core'; + +export interface ModuleOptions { + logger: Logger; +} + +// function wrapLogger(logger: Logger) { +// return { +// log: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// debug: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// info: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// error: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// clientError: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// warn: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// }; +// } + +class CatalogClient { + async list() { + const res = await fetch('http://localhost:7000/catalog/entities'); + if (!res.ok) { + throw new Error(`NOPE, ${await res.text()}`); + } + + return res.json(); + } +} + +export async function createModule( + options: ModuleOptions, +): Promise { + const typeDefs = await fs.promises.readFile( + path.resolve(__dirname, '..', 'schema.gql'), + 'utf-8', + ); + + const catalogClient = new CatalogClient(); + + const resolvers = { + Query: { + catalog: () => true, + }, + CatalogQuery: { + list: () => catalogClient.list(), + }, + CatalogEntity: () => ({}), + }; + + const module = new GraphQLModule({ + typeDefs, + resolvers, + logger: options.logger as any, + }); + + return module; +} diff --git a/plugins/catalog-graphql/src/index.ts b/plugins/catalog-graphql/src/index.ts new file mode 100644 index 0000000000..dd6508f88a --- /dev/null +++ b/plugins/catalog-graphql/src/index.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +require('whatwg-fetch'); + +export * from './graphql/module'; diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql new file mode 100644 index 0000000000..ea92d4e04f --- /dev/null +++ b/plugins/catalog-graphql/src/schema.gql @@ -0,0 +1,25 @@ +type EntityMetadataAnnotation { + key: String! + value: String! +} + +type EntityMetadata { + name: String! + namespace: String + annotation(name: String): EntityMetadataAnnotation + annotations: [EntityMetadataAnnotation!]! +} + +type CatalogEntity { + apiVersion: String + kind: String! + metadata: EntityMetadata +} + +type CatalogQuery { + list: [CatalogEntity!]! +} + +type Query { + catalog: CatalogQuery! +} diff --git a/plugins/catalog-graphql/src/setupTests.ts b/plugins/catalog-graphql/src/setupTests.ts new file mode 100644 index 0000000000..ac41ef995a --- /dev/null +++ b/plugins/catalog-graphql/src/setupTests.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. + */ +require('whatwg-fetch'); + +export {}; diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 02dc9d3608..b68dcf22be 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -21,6 +21,9 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/plugin-catalog-graphql": "^0.1.1-alpha.18", + "@backstage/plugin-bogus-graphql": "^0.1.1-alpha.18", + "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", "apollo-server": "^2.16.0", "apollo-server-express": "^2.16.0", diff --git a/plugins/graphql/schema.gql b/plugins/graphql/schema.gql index 5568182f7f..e69de29bb2 100644 --- a/plugins/graphql/schema.gql +++ b/plugins/graphql/schema.gql @@ -1,11 +0,0 @@ -type CatalogEntity { - id: String -} - -type CatalogQuery { - list: [CatalogEntity!]! -} - -type Query { - catalog: CatalogQuery! -} diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts index 69e2394f11..6845bb366e 100644 --- a/plugins/graphql/src/service/router.ts +++ b/plugins/graphql/src/service/router.ts @@ -20,7 +20,10 @@ import Router from 'express-promise-router'; import { Logger } from 'winston'; import fs from 'fs'; import path from 'path'; +import { GraphQLModule } from '@graphql-modules/core'; import { ApolloServer } from 'apollo-server-express'; +import { createModule as createCatalogModule } from '@backstage/plugin-catalog-graphql'; +import { createModule as createBogusModule } from '@backstage/plugin-bogus-graphql'; const schemaPath = path.resolve( require.resolve('@backstage/plugin-graphql-backend/package.json'), @@ -36,7 +39,14 @@ export async function createRouter( ): Promise { const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); - const server = new ApolloServer({ typeDefs, logger: options.logger }); + const catalogModule = await createCatalogModule(options); + const bogusModule = await createBogusModule(options); + const { schema } = new GraphQLModule({ + imports: [catalogModule, bogusModule], + typeDefs, + }); + + const server = new ApolloServer({ schema, logger: options.logger }); const router = Router(); const apolloMiddlware = server.getMiddleware({ path: '/' }); From 40732f3959517cb3106e268c7525415d319e3e44 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 4 Aug 2020 09:46:56 +0200 Subject: [PATCH 02/34] chore(gql): added some nice stuff with the graphql context --- plugins/catalog-graphql/codegen.yml | 15 + plugins/catalog-graphql/package.json | 5 +- .../catalog-graphql/src/generated-models.ts | 288 ++++++++++++++++++ plugins/catalog-graphql/src/graphql/module.ts | 1 + plugins/catalog-graphql/src/index.ts | 2 - plugins/catalog-graphql/src/setupTests.ts | 2 - 6 files changed, 308 insertions(+), 5 deletions(-) create mode 100644 plugins/catalog-graphql/codegen.yml create mode 100644 plugins/catalog-graphql/src/generated-models.ts diff --git a/plugins/catalog-graphql/codegen.yml b/plugins/catalog-graphql/codegen.yml new file mode 100644 index 0000000000..c9e0173256 --- /dev/null +++ b/plugins/catalog-graphql/codegen.yml @@ -0,0 +1,15 @@ +overwrite: true +schema: ./src/schema.gql # You can use .js files as well +require: + - ts-node/register/transpile-only # required if you're using TS-Node +generates: + ./src/generated-models.ts: + plugins: + - typescript + - typescript-resolvers + + hooks: + afterOneFileWrite: + - prettier --write + config: + contextType: '@graphql-modules/core#ModuleContext' diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 57fade9654..8c478e767b 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -22,12 +22,15 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", + "@graphql-codegen/cli": "^1.17.7", + "@graphql-codegen/typescript": "^1.17.7", + "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", "@types/whatwg-fetch": "^0.0.33", "graphql": "^15.3.0", "node-fetch": "^2.6.0", - "whatwg-fetch": "^2.0.0", + "ts-node": "^8.10.2", "winston": "^3.2.1", "yn": "^4.0.0" }, diff --git a/plugins/catalog-graphql/src/generated-models.ts b/plugins/catalog-graphql/src/generated-models.ts new file mode 100644 index 0000000000..f0f7f2f372 --- /dev/null +++ b/plugins/catalog-graphql/src/generated-models.ts @@ -0,0 +1,288 @@ +/* + * 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 { GraphQLResolveInfo } from 'graphql'; +import { ModuleContext } from '@graphql-modules/core'; + +export type Maybe = T | null; +export type Exact = { + [K in keyof T]: T[K]; +}; +export type RequireFields = { + [X in Exclude]?: T[X]; +} & + { [P in K]-?: NonNullable }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; +}; + +export type EntityMetadataAnnotation = { + __typename?: 'EntityMetadataAnnotation'; + key: Scalars['String']; + value: Scalars['String']; +}; + +export type EntityMetadata = { + __typename?: 'EntityMetadata'; + name: Scalars['String']; + namespace?: Maybe; + annotation?: Maybe; + annotations: Array; +}; + +export type EntityMetadataAnnotationArgs = { + name?: Maybe; +}; + +export type CatalogEntity = { + __typename?: 'CatalogEntity'; + apiVersion?: Maybe; + kind: Scalars['String']; + metadata?: Maybe; +}; + +export type CatalogQuery = { + __typename?: 'CatalogQuery'; + list: Array; +}; + +export type Query = { + __typename?: 'Query'; + catalog: CatalogQuery; +}; + +export type ResolverTypeWrapper = Promise | T; + +export type LegacyStitchingResolver = { + fragment: string; + resolve: ResolverFn; +}; + +export type NewStitchingResolver = { + selectionSet: string; + resolve: ResolverFn; +}; +export type StitchingResolver = + | LegacyStitchingResolver + | NewStitchingResolver; +export type Resolver = + | ResolverFn + | StitchingResolver; + +export type ResolverFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo, +) => Promise | TResult; + +export type SubscriptionSubscribeFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo, +) => AsyncIterator | Promise>; + +export type SubscriptionResolveFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo, +) => TResult | Promise; + +export interface SubscriptionSubscriberObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs +> { + subscribe: SubscriptionSubscribeFn< + { [key in TKey]: TResult }, + TParent, + TContext, + TArgs + >; + resolve?: SubscriptionResolveFn< + TResult, + { [key in TKey]: TResult }, + TContext, + TArgs + >; +} + +export interface SubscriptionResolverObject { + subscribe: SubscriptionSubscribeFn; + resolve: SubscriptionResolveFn; +} + +export type SubscriptionObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs +> = + | SubscriptionSubscriberObject + | SubscriptionResolverObject; + +export type SubscriptionResolver< + TResult, + TKey extends string, + TParent = {}, + TContext = {}, + TArgs = {} +> = + | (( + ...args: any[] + ) => SubscriptionObject) + | SubscriptionObject; + +export type TypeResolveFn = ( + parent: TParent, + context: TContext, + info: GraphQLResolveInfo, +) => Maybe | Promise>; + +export type IsTypeOfResolverFn = ( + obj: T, + info: GraphQLResolveInfo, +) => boolean | Promise; + +export type NextResolverFn = () => Promise; + +export type DirectiveResolverFn< + TResult = {}, + TParent = {}, + TContext = {}, + TArgs = {} +> = ( + next: NextResolverFn, + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo, +) => TResult | Promise; + +/** Mapping between all available schema types and the resolvers types */ +export type ResolversTypes = { + EntityMetadataAnnotation: ResolverTypeWrapper; + String: ResolverTypeWrapper; + EntityMetadata: ResolverTypeWrapper; + CatalogEntity: ResolverTypeWrapper; + CatalogQuery: ResolverTypeWrapper; + Query: ResolverTypeWrapper<{}>; + Boolean: ResolverTypeWrapper; +}; + +/** Mapping between all available schema types and the resolvers parents */ +export type ResolversParentTypes = { + EntityMetadataAnnotation: EntityMetadataAnnotation; + String: Scalars['String']; + EntityMetadata: EntityMetadata; + CatalogEntity: CatalogEntity; + CatalogQuery: CatalogQuery; + Query: {}; + Boolean: Scalars['Boolean']; +}; + +export type EntityMetadataAnnotationResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['EntityMetadataAnnotation'] = ResolversParentTypes['EntityMetadataAnnotation'] +> = { + key?: Resolver; + value?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type EntityMetadataResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['EntityMetadata'] = ResolversParentTypes['EntityMetadata'] +> = { + name?: Resolver; + namespace?: Resolver< + Maybe, + ParentType, + ContextType + >; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + annotations?: Resolver< + Array, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type CatalogEntityResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['CatalogEntity'] = ResolversParentTypes['CatalogEntity'] +> = { + apiVersion?: Resolver< + Maybe, + ParentType, + ContextType + >; + kind?: Resolver; + metadata?: Resolver< + Maybe, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type CatalogQueryResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['CatalogQuery'] = ResolversParentTypes['CatalogQuery'] +> = { + list?: Resolver< + Array, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type QueryResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query'] +> = { + catalog?: Resolver; +}; + +export type Resolvers = { + EntityMetadataAnnotation?: EntityMetadataAnnotationResolvers; + EntityMetadata?: EntityMetadataResolvers; + CatalogEntity?: CatalogEntityResolvers; + CatalogQuery?: CatalogQueryResolvers; + Query?: QueryResolvers; +}; + +/** + * @deprecated + * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. + */ +export type IResolvers = Resolvers; diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 65239d27db..7648b7d302 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -19,6 +19,7 @@ import fetch from 'node-fetch'; import fs from 'fs'; import path from 'path'; import { GraphQLModule } from '@graphql-modules/core'; +import { QueryResolvers } from './types'; export interface ModuleOptions { logger: Logger; diff --git a/plugins/catalog-graphql/src/index.ts b/plugins/catalog-graphql/src/index.ts index dd6508f88a..2e3f1c1fe2 100644 --- a/plugins/catalog-graphql/src/index.ts +++ b/plugins/catalog-graphql/src/index.ts @@ -14,6 +14,4 @@ * limitations under the License. */ -require('whatwg-fetch'); - export * from './graphql/module'; diff --git a/plugins/catalog-graphql/src/setupTests.ts b/plugins/catalog-graphql/src/setupTests.ts index ac41ef995a..1485402d0d 100644 --- a/plugins/catalog-graphql/src/setupTests.ts +++ b/plugins/catalog-graphql/src/setupTests.ts @@ -13,6 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -require('whatwg-fetch'); - export {}; From 49769569aa118a2c0590c73f9499ab7bb96fce7b Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Aug 2020 07:26:44 +0200 Subject: [PATCH 03/34] chore(gql): removing old graphql things --- plugins/bogus-graphql/.eslintrc.js | 3 -- plugins/bogus-graphql/README.md | 13 ------ plugins/bogus-graphql/package.json | 44 ------------------- plugins/bogus-graphql/scripts/mock-data.sh | 2 - plugins/bogus-graphql/src/graphql/module.ts | 47 --------------------- plugins/bogus-graphql/src/index.ts | 19 --------- plugins/bogus-graphql/src/schema.gql | 3 -- plugins/bogus-graphql/src/setupTests.ts | 18 -------- 8 files changed, 149 deletions(-) delete mode 100644 plugins/bogus-graphql/.eslintrc.js delete mode 100644 plugins/bogus-graphql/README.md delete mode 100644 plugins/bogus-graphql/package.json delete mode 100755 plugins/bogus-graphql/scripts/mock-data.sh delete mode 100644 plugins/bogus-graphql/src/graphql/module.ts delete mode 100644 plugins/bogus-graphql/src/index.ts delete mode 100644 plugins/bogus-graphql/src/schema.gql delete mode 100644 plugins/bogus-graphql/src/setupTests.ts diff --git a/plugins/bogus-graphql/.eslintrc.js b/plugins/bogus-graphql/.eslintrc.js deleted file mode 100644 index 16a033dbc6..0000000000 --- a/plugins/bogus-graphql/.eslintrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint.backend')], -}; diff --git a/plugins/bogus-graphql/README.md b/plugins/bogus-graphql/README.md deleted file mode 100644 index 3f85ec766e..0000000000 --- a/plugins/bogus-graphql/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# graphql - -Welcome to the graphql backend plugin! - -_This plugin was created through the Backstage CLI_ - -## Getting started - -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/graphql](http://localhost:3000/graphql). - -You can also serve the plugin in isolation by running `yarn start` in the plugin directory. -This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. diff --git a/plugins/bogus-graphql/package.json b/plugins/bogus-graphql/package.json deleted file mode 100644 index a5a439a701..0000000000 --- a/plugins/bogus-graphql/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@backstage/plugin-bogus-graphql", - "version": "0.1.1-alpha.16", - "main": "src/index.ts", - "types": "src/index.ts", - "license": "Apache-2.0", - "private": true, - "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" - }, - "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean", - "mock-data": "./scripts/mock-data.sh" - }, - "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.16", - "@graphql-modules/core": "^0.7.17", - "@types/express": "^4.17.6", - "@types/whatwg-fetch": "^0.0.33", - "graphql": "^15.3.0", - "node-fetch": "^2.6.0", - "whatwg-fetch": "^2.0.0", - "winston": "^3.2.1", - "yn": "^4.0.0" - }, - "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.16", - "@types/supertest": "^2.0.8", - "eslint-plugin-graphql": "^4.0.0", - "msw": "^0.19.5", - "supertest": "^4.0.2" - }, - "files": [ - "dist" - ] -} diff --git a/plugins/bogus-graphql/scripts/mock-data.sh b/plugins/bogus-graphql/scripts/mock-data.sh deleted file mode 100755 index ff30921715..0000000000 --- a/plugins/bogus-graphql/scripts/mock-data.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo "use this script to load your service with some mock data if needed!" diff --git a/plugins/bogus-graphql/src/graphql/module.ts b/plugins/bogus-graphql/src/graphql/module.ts deleted file mode 100644 index f247686ea8..0000000000 --- a/plugins/bogus-graphql/src/graphql/module.ts +++ /dev/null @@ -1,47 +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 { Logger } from 'winston'; -import fs from 'fs'; -import path from 'path'; -import { GraphQLModule } from '@graphql-modules/core'; - -export interface ModuleOptions { - logger: Logger; -} - -export async function createModule( - options: ModuleOptions, -): Promise { - const typeDefs = await fs.promises.readFile( - path.resolve(__dirname, '..', 'schema.gql'), - 'utf-8', - ); - - const resolvers = { - EntityMetadata: { - uppercaseName: metadata => metadata.name.toUpperCase(), - }, - }; - - const module = new GraphQLModule({ - typeDefs, - resolvers, - logger: options.logger as any, - }); - - return module; -} diff --git a/plugins/bogus-graphql/src/index.ts b/plugins/bogus-graphql/src/index.ts deleted file mode 100644 index dd6508f88a..0000000000 --- a/plugins/bogus-graphql/src/index.ts +++ /dev/null @@ -1,19 +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. - */ - -require('whatwg-fetch'); - -export * from './graphql/module'; diff --git a/plugins/bogus-graphql/src/schema.gql b/plugins/bogus-graphql/src/schema.gql deleted file mode 100644 index 74c0853a92..0000000000 --- a/plugins/bogus-graphql/src/schema.gql +++ /dev/null @@ -1,3 +0,0 @@ -type EntityMetadata { - uppercaseName: String -} diff --git a/plugins/bogus-graphql/src/setupTests.ts b/plugins/bogus-graphql/src/setupTests.ts deleted file mode 100644 index ac41ef995a..0000000000 --- a/plugins/bogus-graphql/src/setupTests.ts +++ /dev/null @@ -1,18 +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. - */ -require('whatwg-fetch'); - -export {}; From 4567ae32871dcfc7279af1ecb2aeda18a1e6195d Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Aug 2020 07:27:07 +0200 Subject: [PATCH 04/34] chore(gql): pass through the config for the catalog client --- plugins/graphql/package.json | 2 +- plugins/graphql/src/service/router.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index b68dcf22be..a5b368eef1 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/config": "^0.1.1-alpha.18", "@backstage/plugin-catalog-graphql": "^0.1.1-alpha.18", - "@backstage/plugin-bogus-graphql": "^0.1.1-alpha.18", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", "apollo-server": "^2.16.0", diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts index 6845bb366e..784b801b86 100644 --- a/plugins/graphql/src/service/router.ts +++ b/plugins/graphql/src/service/router.ts @@ -23,7 +23,7 @@ import path from 'path'; import { GraphQLModule } from '@graphql-modules/core'; import { ApolloServer } from 'apollo-server-express'; import { createModule as createCatalogModule } from '@backstage/plugin-catalog-graphql'; -import { createModule as createBogusModule } from '@backstage/plugin-bogus-graphql'; +import { Config } from '@backstage/config'; const schemaPath = path.resolve( require.resolve('@backstage/plugin-graphql-backend/package.json'), @@ -32,6 +32,7 @@ const schemaPath = path.resolve( export interface RouterOptions { logger: Logger; + config: Config; } export async function createRouter( @@ -40,9 +41,8 @@ export async function createRouter( const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); const catalogModule = await createCatalogModule(options); - const bogusModule = await createBogusModule(options); const { schema } = new GraphQLModule({ - imports: [catalogModule, bogusModule], + imports: [catalogModule], typeDefs, }); From e4986c4eef13e7d5b40f84d8f3237c34caaf6826 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Aug 2020 07:27:32 +0200 Subject: [PATCH 05/34] chore(gql): tidying up the graphql code --- plugins/catalog-graphql/.gitignore | 1 + plugins/catalog-graphql/codegen.yml | 11 +- plugins/catalog-graphql/package.json | 3 + .../catalog-graphql/src/generated-models.ts | 288 ------------------ plugins/catalog-graphql/src/graphql/module.ts | 61 ++-- plugins/catalog-graphql/src/schema.gql | 2 +- plugins/catalog-graphql/src/service/client.ts | 30 ++ 7 files changed, 68 insertions(+), 328 deletions(-) create mode 100644 plugins/catalog-graphql/.gitignore delete mode 100644 plugins/catalog-graphql/src/generated-models.ts create mode 100644 plugins/catalog-graphql/src/service/client.ts diff --git a/plugins/catalog-graphql/.gitignore b/plugins/catalog-graphql/.gitignore new file mode 100644 index 0000000000..5a5538170a --- /dev/null +++ b/plugins/catalog-graphql/.gitignore @@ -0,0 +1 @@ +src/graphql/types.ts diff --git a/plugins/catalog-graphql/codegen.yml b/plugins/catalog-graphql/codegen.yml index c9e0173256..ebdb34a938 100644 --- a/plugins/catalog-graphql/codegen.yml +++ b/plugins/catalog-graphql/codegen.yml @@ -1,15 +1,16 @@ overwrite: true -schema: ./src/schema.gql # You can use .js files as well +schema: ./src/schema.gql require: - - ts-node/register/transpile-only # required if you're using TS-Node + - ts-node/register/transpile-only generates: - ./src/generated-models.ts: + ./src/graphql/types.ts: plugins: - typescript - typescript-resolvers - hooks: afterOneFileWrite: - - prettier --write + - eslint --fix config: contextType: '@graphql-modules/core#ModuleContext' + useIndexSignature: true + defaultMapper: Partial<{T}> diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 8c478e767b..8a0c0474f6 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -17,11 +17,14 @@ "test": "backstage-cli test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", + "postinstall": "graphql-codegen", "clean": "backstage-cli clean", "mock-data": "./scripts/mock-data.sh" }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", + "@backstage/catalog-model": "^0.1.1-alpha.16", + "@backstage/config": "^0.1.1-alpha.16", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", diff --git a/plugins/catalog-graphql/src/generated-models.ts b/plugins/catalog-graphql/src/generated-models.ts deleted file mode 100644 index f0f7f2f372..0000000000 --- a/plugins/catalog-graphql/src/generated-models.ts +++ /dev/null @@ -1,288 +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 { GraphQLResolveInfo } from 'graphql'; -import { ModuleContext } from '@graphql-modules/core'; - -export type Maybe = T | null; -export type Exact = { - [K in keyof T]: T[K]; -}; -export type RequireFields = { - [X in Exclude]?: T[X]; -} & - { [P in K]-?: NonNullable }; -/** All built-in and custom scalars, mapped to their actual values */ -export type Scalars = { - ID: string; - String: string; - Boolean: boolean; - Int: number; - Float: number; -}; - -export type EntityMetadataAnnotation = { - __typename?: 'EntityMetadataAnnotation'; - key: Scalars['String']; - value: Scalars['String']; -}; - -export type EntityMetadata = { - __typename?: 'EntityMetadata'; - name: Scalars['String']; - namespace?: Maybe; - annotation?: Maybe; - annotations: Array; -}; - -export type EntityMetadataAnnotationArgs = { - name?: Maybe; -}; - -export type CatalogEntity = { - __typename?: 'CatalogEntity'; - apiVersion?: Maybe; - kind: Scalars['String']; - metadata?: Maybe; -}; - -export type CatalogQuery = { - __typename?: 'CatalogQuery'; - list: Array; -}; - -export type Query = { - __typename?: 'Query'; - catalog: CatalogQuery; -}; - -export type ResolverTypeWrapper = Promise | T; - -export type LegacyStitchingResolver = { - fragment: string; - resolve: ResolverFn; -}; - -export type NewStitchingResolver = { - selectionSet: string; - resolve: ResolverFn; -}; -export type StitchingResolver = - | LegacyStitchingResolver - | NewStitchingResolver; -export type Resolver = - | ResolverFn - | StitchingResolver; - -export type ResolverFn = ( - parent: TParent, - args: TArgs, - context: TContext, - info: GraphQLResolveInfo, -) => Promise | TResult; - -export type SubscriptionSubscribeFn = ( - parent: TParent, - args: TArgs, - context: TContext, - info: GraphQLResolveInfo, -) => AsyncIterator | Promise>; - -export type SubscriptionResolveFn = ( - parent: TParent, - args: TArgs, - context: TContext, - info: GraphQLResolveInfo, -) => TResult | Promise; - -export interface SubscriptionSubscriberObject< - TResult, - TKey extends string, - TParent, - TContext, - TArgs -> { - subscribe: SubscriptionSubscribeFn< - { [key in TKey]: TResult }, - TParent, - TContext, - TArgs - >; - resolve?: SubscriptionResolveFn< - TResult, - { [key in TKey]: TResult }, - TContext, - TArgs - >; -} - -export interface SubscriptionResolverObject { - subscribe: SubscriptionSubscribeFn; - resolve: SubscriptionResolveFn; -} - -export type SubscriptionObject< - TResult, - TKey extends string, - TParent, - TContext, - TArgs -> = - | SubscriptionSubscriberObject - | SubscriptionResolverObject; - -export type SubscriptionResolver< - TResult, - TKey extends string, - TParent = {}, - TContext = {}, - TArgs = {} -> = - | (( - ...args: any[] - ) => SubscriptionObject) - | SubscriptionObject; - -export type TypeResolveFn = ( - parent: TParent, - context: TContext, - info: GraphQLResolveInfo, -) => Maybe | Promise>; - -export type IsTypeOfResolverFn = ( - obj: T, - info: GraphQLResolveInfo, -) => boolean | Promise; - -export type NextResolverFn = () => Promise; - -export type DirectiveResolverFn< - TResult = {}, - TParent = {}, - TContext = {}, - TArgs = {} -> = ( - next: NextResolverFn, - parent: TParent, - args: TArgs, - context: TContext, - info: GraphQLResolveInfo, -) => TResult | Promise; - -/** Mapping between all available schema types and the resolvers types */ -export type ResolversTypes = { - EntityMetadataAnnotation: ResolverTypeWrapper; - String: ResolverTypeWrapper; - EntityMetadata: ResolverTypeWrapper; - CatalogEntity: ResolverTypeWrapper; - CatalogQuery: ResolverTypeWrapper; - Query: ResolverTypeWrapper<{}>; - Boolean: ResolverTypeWrapper; -}; - -/** Mapping between all available schema types and the resolvers parents */ -export type ResolversParentTypes = { - EntityMetadataAnnotation: EntityMetadataAnnotation; - String: Scalars['String']; - EntityMetadata: EntityMetadata; - CatalogEntity: CatalogEntity; - CatalogQuery: CatalogQuery; - Query: {}; - Boolean: Scalars['Boolean']; -}; - -export type EntityMetadataAnnotationResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['EntityMetadataAnnotation'] = ResolversParentTypes['EntityMetadataAnnotation'] -> = { - key?: Resolver; - value?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type EntityMetadataResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['EntityMetadata'] = ResolversParentTypes['EntityMetadata'] -> = { - name?: Resolver; - namespace?: Resolver< - Maybe, - ParentType, - ContextType - >; - annotation?: Resolver< - Maybe, - ParentType, - ContextType, - RequireFields - >; - annotations?: Resolver< - Array, - ParentType, - ContextType - >; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type CatalogEntityResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['CatalogEntity'] = ResolversParentTypes['CatalogEntity'] -> = { - apiVersion?: Resolver< - Maybe, - ParentType, - ContextType - >; - kind?: Resolver; - metadata?: Resolver< - Maybe, - ParentType, - ContextType - >; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type CatalogQueryResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['CatalogQuery'] = ResolversParentTypes['CatalogQuery'] -> = { - list?: Resolver< - Array, - ParentType, - ContextType - >; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type QueryResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query'] -> = { - catalog?: Resolver; -}; - -export type Resolvers = { - EntityMetadataAnnotation?: EntityMetadataAnnotationResolvers; - EntityMetadata?: EntityMetadataResolvers; - CatalogEntity?: CatalogEntityResolvers; - CatalogQuery?: CatalogQueryResolvers; - Query?: QueryResolvers; -}; - -/** - * @deprecated - * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. - */ -export type IResolvers = Resolvers; diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 7648b7d302..1fc13d7950 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -15,43 +15,28 @@ */ import { Logger } from 'winston'; -import fetch from 'node-fetch'; import fs from 'fs'; import path from 'path'; import { GraphQLModule } from '@graphql-modules/core'; -import { QueryResolvers } from './types'; +import { Resolvers, CatalogEntity, CatalogQuery } from './types'; +import { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { CatalogClient } from '../service/client'; export interface ModuleOptions { logger: Logger; + config: Config; } -// function wrapLogger(logger: Logger) { -// return { -// log: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// debug: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// info: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// error: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// clientError: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// warn: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// }; -// } - -class CatalogClient { - async list() { - const res = await fetch('http://localhost:7000/catalog/entities'); - if (!res.ok) { - throw new Error(`NOPE, ${await res.text()}`); - } - - return res.json(); - } -} +const parseToCatalogEntities = (e: Entity): CatalogEntity => ({ + ...e, + metadata: { + ...e.metadata, + annotations: Object.entries( + e.metadata.annotations ?? {}, + ).map(([key, value]) => ({ key, value })), + }, +}); export async function createModule( options: ModuleOptions, @@ -61,16 +46,24 @@ export async function createModule( 'utf-8', ); - const catalogClient = new CatalogClient(); + const catalogClient = new CatalogClient( + options.config.getString('backend.baseUrl'), + ); - const resolvers = { + const resolvers: Resolvers = { Query: { - catalog: () => true, + catalog: () => ({} as CatalogQuery), }, CatalogQuery: { - list: () => catalogClient.list(), + list: async () => { + const list = await catalogClient.list(); + return list.map(parseToCatalogEntities); + }, + }, + EntityMetadata: { + annotation: (e, { name }) => + e.annotations?.find(a => a.key === name) ?? null, }, - CatalogEntity: () => ({}), }; const module = new GraphQLModule({ diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql index ea92d4e04f..b28f796855 100644 --- a/plugins/catalog-graphql/src/schema.gql +++ b/plugins/catalog-graphql/src/schema.gql @@ -7,7 +7,7 @@ type EntityMetadata { name: String! namespace: String annotation(name: String): EntityMetadataAnnotation - annotations: [EntityMetadataAnnotation!]! + annotations: [EntityMetadataAnnotation!] } type CatalogEntity { diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts new file mode 100644 index 0000000000..091a8a8cc0 --- /dev/null +++ b/plugins/catalog-graphql/src/service/client.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import fetch from 'node-fetch'; + +export class CatalogClient { + constructor(private baseUrl: string) {} + async list(): Promise { + const res = await fetch(`${this.baseUrl}/catalog/entities`); + if (!res.ok) { + throw new Error(`NOPE, ${await res.text()}`); + } + + return await (res.json() as Promise); + } +} From ac3ea688fb5425a927029cddc54b80c4ac15e4aa Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Aug 2020 07:27:51 +0200 Subject: [PATCH 06/34] gotcha(gql): if you just import the type the config library doesnt work :shrug: --- packages/backend/src/index.ts | 1 - packages/backend/src/plugins/graphql.ts | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 50f59795fc..1d5211597d 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -60,7 +60,6 @@ async function main() { const configs = await loadBackendConfig(); const configReader = ConfigReader.fromConfigs(configs); const createEnv = makeCreateEnv(configs); - const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck')); const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); diff --git a/packages/backend/src/plugins/graphql.ts b/packages/backend/src/plugins/graphql.ts index 0f9b167d19..d52e28f9cb 100644 --- a/packages/backend/src/plugins/graphql.ts +++ b/packages/backend/src/plugins/graphql.ts @@ -30,10 +30,14 @@ */ import { createRouter } from '@backstage/plugin-graphql-backend'; -import type { PluginEnvironment } from '../types'; +import { PluginEnvironment } from '../types'; -export default async function createPlugin({ logger }: PluginEnvironment) { +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { return await createRouter({ logger, + config, }); } From d20e0dd1e90b6b30b117e262c353f6ce8f78f8f9 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 10:37:39 +0200 Subject: [PATCH 07/34] chore(gql): fixing dependencies now there is a new release --- plugins/catalog-graphql/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 8a0c0474f6..f64ba1404e 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.1.1-alpha.16", + "version": "0.1.1-alpha.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,9 +22,9 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.16", - "@backstage/catalog-model": "^0.1.1-alpha.16", - "@backstage/config": "^0.1.1-alpha.16", + "@backstage/backend-common": "^0.1.1-alpha.17", + "@backstage/catalog-model": "^0.1.1-alpha.17", + "@backstage/config": "^0.1.1-alpha.17", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", @@ -38,7 +38,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/cli": "^0.1.1-alpha.17", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.19.5", From 55dcef1387feb75f6512e0d923cb79b182ac333f Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 10:38:06 +0200 Subject: [PATCH 08/34] chore(gql): make catalog-gql non-private --- plugins/catalog-graphql/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index f64ba1404e..205e35aba6 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -4,7 +4,6 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", - "private": true, "publishConfig": { "access": "public", "main": "dist/index.esm.js", From b82705efadf59aeddacbda5268a0242431557625 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 14:46:00 +0200 Subject: [PATCH 09/34] chore(gql): fixing build issues --- plugins/catalog-graphql/package.json | 1 - plugins/graphql/package.json | 3 +- plugins/graphql/src/index.ts | 2 - plugins/graphql/src/run.ts | 33 ---------- .../graphql/src/service/standaloneServer.ts | 62 ------------------- plugins/graphql/src/setupTests.ts | 1 - yarn.lock | 8 +++ 7 files changed, 10 insertions(+), 100 deletions(-) delete mode 100644 plugins/graphql/src/run.ts delete mode 100644 plugins/graphql/src/service/standaloneServer.ts diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 205e35aba6..6f3c2c9aaa 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -29,7 +29,6 @@ "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", - "@types/whatwg-fetch": "^0.0.33", "graphql": "^15.3.0", "node-fetch": "^2.6.0", "ts-node": "^8.10.2", diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index a5b368eef1..548a4c407f 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -30,7 +30,8 @@ "express": "^4.17.1", "express-promise-router": "^3.0.3", "graphql": "^15.3.0", - "whatwg-fetch": "^2.0.0", + "node-fetch": "^2.6.0", + "reflect-metadata": "^0.1.13", "winston": "^3.2.1", "yn": "^4.0.0" }, diff --git a/plugins/graphql/src/index.ts b/plugins/graphql/src/index.ts index f12e894c84..7612c392a2 100644 --- a/plugins/graphql/src/index.ts +++ b/plugins/graphql/src/index.ts @@ -14,6 +14,4 @@ * limitations under the License. */ -require('whatwg-fetch'); - export * from './service/router'; diff --git a/plugins/graphql/src/run.ts b/plugins/graphql/src/run.ts deleted file mode 100644 index b96989e4b8..0000000000 --- a/plugins/graphql/src/run.ts +++ /dev/null @@ -1,33 +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 { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/graphql/src/service/standaloneServer.ts b/plugins/graphql/src/service/standaloneServer.ts deleted file mode 100644 index 0f772a583f..0000000000 --- a/plugins/graphql/src/service/standaloneServer.ts +++ /dev/null @@ -1,62 +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. - */ -/* - * 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 { createServiceBuilder } from '@backstage/backend-common'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'graphql-backend' }); - logger.debug('Starting application server...'); - const router = await createRouter({ - logger, - }); - - const service = createServiceBuilder(module) - .enableCors({ origin: 'http://localhost:3000' }) - .addRouter('/graphql', router); - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/graphql/src/setupTests.ts b/plugins/graphql/src/setupTests.ts index ac41ef995a..ba33cf996b 100644 --- a/plugins/graphql/src/setupTests.ts +++ b/plugins/graphql/src/setupTests.ts @@ -13,6 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -require('whatwg-fetch'); export {}; diff --git a/yarn.lock b/yarn.lock index 58084fb145..10a6f0d7b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19377,6 +19377,11 @@ redux@^4.0.4, redux@^4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + refractor@^2.4.1: version "2.10.1" resolved "https://registry.npmjs.org/refractor/-/refractor-2.10.1.tgz#166c32f114ed16fd96190ad21d5193d3afc7d34e" @@ -23015,6 +23020,7 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +<<<<<<< HEAD whatwg-url@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-2.0.1.tgz#5396b2043f020ee6f704d9c45ea8519e724de659" @@ -23023,6 +23029,8 @@ whatwg-url@^2.0.1: tr46 "~0.0.3" webidl-conversions "^3.0.0" +======= +>>>>>>> chore(gql): fixing build issues whatwg-url@^6.4.1: version "6.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" From 101236b991760903506e4fc0ca922aa9ac5e6161 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 15:22:58 +0200 Subject: [PATCH 10/34] chore(gql): add todo --- plugins/catalog-graphql/src/service/client.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts index 091a8a8cc0..eddae2f396 100644 --- a/plugins/catalog-graphql/src/service/client.ts +++ b/plugins/catalog-graphql/src/service/client.ts @@ -22,6 +22,8 @@ export class CatalogClient { async list(): Promise { const res = await fetch(`${this.baseUrl}/catalog/entities`); if (!res.ok) { + // todo(blam): need some better way to handle errors here + // experiment with throwing the input errors etc and having graphql versions of that throw new Error(`NOPE, ${await res.text()}`); } From 35a423ee88580fae887a54a7eaf12e55bf24ec6b Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 15:36:21 +0200 Subject: [PATCH 11/34] chore(gql): fixing the actual build issue --- plugins/catalog-graphql/package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 6f3c2c9aaa..fec113f545 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -28,7 +28,6 @@ "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", - "@types/express": "^4.17.6", "graphql": "^15.3.0", "node-fetch": "^2.6.0", "ts-node": "^8.10.2", @@ -39,6 +38,7 @@ "@backstage/cli": "^0.1.1-alpha.17", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", + "@types/express": "^4.17.7", "msw": "^0.19.5", "supertest": "^4.0.2" }, diff --git a/yarn.lock b/yarn.lock index 10a6f0d7b2..fe0a3374e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4611,7 +4611,7 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express@*", "@types/express@4.17.7", "@types/express@^4.17.6": +"@types/express@*", "@types/express@^4.17.6", "@types/express@^4.17.7": version "4.17.7" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ== From 295faeab988228727af6b4be7cdd0495763a62bc Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 16:02:28 +0200 Subject: [PATCH 12/34] feat(gql): move out the generation into a one time command --- plugins/catalog-graphql/.gitignore | 1 - plugins/catalog-graphql/package.json | 2 +- plugins/catalog-graphql/src/graphql/types.ts | 209 +++++++++++++++++++ 3 files changed, 210 insertions(+), 2 deletions(-) delete mode 100644 plugins/catalog-graphql/.gitignore create mode 100644 plugins/catalog-graphql/src/graphql/types.ts diff --git a/plugins/catalog-graphql/.gitignore b/plugins/catalog-graphql/.gitignore deleted file mode 100644 index 5a5538170a..0000000000 --- a/plugins/catalog-graphql/.gitignore +++ /dev/null @@ -1 +0,0 @@ -src/graphql/types.ts diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index fec113f545..cb8f900bff 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -16,7 +16,7 @@ "test": "backstage-cli test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "postinstall": "graphql-codegen", + "generate:types": "graphql-codegen", "clean": "backstage-cli clean", "mock-data": "./scripts/mock-data.sh" }, diff --git a/plugins/catalog-graphql/src/graphql/types.ts b/plugins/catalog-graphql/src/graphql/types.ts new file mode 100644 index 0000000000..567e19c8bb --- /dev/null +++ b/plugins/catalog-graphql/src/graphql/types.ts @@ -0,0 +1,209 @@ +/* + * 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 { GraphQLResolveInfo } from 'graphql'; +import { ModuleContext } from '@graphql-modules/core'; + +export type Maybe = T | null; +export type Exact = { [K in keyof T]: T[K] }; +export type RequireFields = { [X in Exclude]?: T[X] } & { [P in K]-?: NonNullable }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; +}; + +export type EntityMetadataAnnotation = { + __typename?: 'EntityMetadataAnnotation'; + key: Scalars['String']; + value: Scalars['String']; +}; + +export type EntityMetadata = { + __typename?: 'EntityMetadata'; + name: Scalars['String']; + namespace?: Maybe; + annotation?: Maybe; + annotations?: Maybe>; +}; + + +export type EntityMetadataAnnotationArgs = { + name?: Maybe; +}; + +export type CatalogEntity = { + __typename?: 'CatalogEntity'; + apiVersion?: Maybe; + kind: Scalars['String']; + metadata?: Maybe; +}; + +export type CatalogQuery = { + __typename?: 'CatalogQuery'; + list: Array; +}; + +export type Query = { + __typename?: 'Query'; + catalog: CatalogQuery; +}; + +export type WithIndex = TObject & Record; +export type ResolversObject = WithIndex; + +export type ResolverTypeWrapper = Promise | T; + + +export type LegacyStitchingResolver = { + fragment: string; + resolve: ResolverFn; +}; + +export type NewStitchingResolver = { + selectionSet: string; + resolve: ResolverFn; +}; +export type StitchingResolver = LegacyStitchingResolver | NewStitchingResolver; +export type Resolver = + | ResolverFn + | StitchingResolver; + +export type ResolverFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => Promise | TResult; + +export type SubscriptionSubscribeFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => AsyncIterator | Promise>; + +export type SubscriptionResolveFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + +export interface SubscriptionSubscriberObject { + subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; + resolve?: SubscriptionResolveFn; +} + +export interface SubscriptionResolverObject { + subscribe: SubscriptionSubscribeFn; + resolve: SubscriptionResolveFn; +} + +export type SubscriptionObject = + | SubscriptionSubscriberObject + | SubscriptionResolverObject; + +export type SubscriptionResolver = + | ((...args: any[]) => SubscriptionObject) + | SubscriptionObject; + +export type TypeResolveFn = ( + parent: TParent, + context: TContext, + info: GraphQLResolveInfo +) => Maybe | Promise>; + +export type IsTypeOfResolverFn = (obj: T, info: GraphQLResolveInfo) => boolean | Promise; + +export type NextResolverFn = () => Promise; + +export type DirectiveResolverFn = ( + next: NextResolverFn, + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + +/** Mapping between all available schema types and the resolvers types */ +export type ResolversTypes = ResolversObject<{ + EntityMetadataAnnotation: ResolverTypeWrapper>; + String: ResolverTypeWrapper>; + EntityMetadata: ResolverTypeWrapper>; + CatalogEntity: ResolverTypeWrapper>; + CatalogQuery: ResolverTypeWrapper>; + Query: ResolverTypeWrapper<{}>; + Boolean: ResolverTypeWrapper>; +}>; + +/** Mapping between all available schema types and the resolvers parents */ +export type ResolversParentTypes = ResolversObject<{ + EntityMetadataAnnotation: Partial; + String: Partial; + EntityMetadata: Partial; + CatalogEntity: Partial; + CatalogQuery: Partial; + Query: {}; + Boolean: Partial; +}>; + +export type EntityMetadataAnnotationResolvers = ResolversObject<{ + key?: Resolver; + value?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type EntityMetadataResolvers = ResolversObject<{ + name?: Resolver; + namespace?: Resolver, ParentType, ContextType>; + annotation?: Resolver, ParentType, ContextType, RequireFields>; + annotations?: Resolver>, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type CatalogEntityResolvers = ResolversObject<{ + apiVersion?: Resolver, ParentType, ContextType>; + kind?: Resolver; + metadata?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type CatalogQueryResolvers = ResolversObject<{ + list?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type QueryResolvers = ResolversObject<{ + catalog?: Resolver; +}>; + +export type Resolvers = ResolversObject<{ + EntityMetadataAnnotation?: EntityMetadataAnnotationResolvers; + EntityMetadata?: EntityMetadataResolvers; + CatalogEntity?: CatalogEntityResolvers; + CatalogQuery?: CatalogQueryResolvers; + Query?: QueryResolvers; +}>; + + +/** + * @deprecated + * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. + */ +export type IResolvers = Resolvers; From d13164eec93bb4a9182104a8cd8e9a43f8051c75 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 16:21:57 +0200 Subject: [PATCH 13/34] test(gql): added some mock tests for now --- .../src/service/client.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 plugins/catalog-graphql/src/service/client.test.ts diff --git a/plugins/catalog-graphql/src/service/client.test.ts b/plugins/catalog-graphql/src/service/client.test.ts new file mode 100644 index 0000000000..e249bf1c15 --- /dev/null +++ b/plugins/catalog-graphql/src/service/client.test.ts @@ -0,0 +1,23 @@ +/* + * 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 { CatalogClient } from './client'; + +describe('Catalog GraphQL Module', () => { + it('create a new client', () => { + void new CatalogClient('http://localhost:1234'); + expect(true).toBe(true); + }); +}); From 0f95949d12719d5fda970ffccee3513748f8a27e Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 10:32:18 +0200 Subject: [PATCH 14/34] chore(gql/catalog): updating the version deps after new buymp --- plugins/catalog-graphql/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index cb8f900bff..7c8b73a943 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.1.1-alpha.17", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.17", - "@backstage/catalog-model": "^0.1.1-alpha.17", - "@backstage/config": "^0.1.1-alpha.17", + "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/config": "^0.1.1-alpha.18", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.17", + "@backstage/cli": "^0.1.1-alpha.18", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "@types/express": "^4.17.7", From 8f14645a1bc7e089dfcd35bf1c624498550662a1 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 10:51:46 +0200 Subject: [PATCH 15/34] feat(gql/catlaog): Tidying up the resolver for the annotations --- plugins/catalog-graphql/package.json | 3 +- plugins/catalog-graphql/src/graphql/module.ts | 23 +++---- plugins/catalog-graphql/src/schema.gql | 6 +- yarn.lock | 63 +++++++++++++++++++ 4 files changed, 76 insertions(+), 19 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 7c8b73a943..be5c585186 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -29,6 +29,7 @@ "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", "graphql": "^15.3.0", + "graphql-type-json": "^0.3.2", "node-fetch": "^2.6.0", "ts-node": "^8.10.2", "winston": "^3.2.1", @@ -36,9 +37,9 @@ }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.18", + "@types/express": "^4.17.7", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", - "@types/express": "^4.17.7", "msw": "^0.19.5", "supertest": "^4.0.2" }, diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 1fc13d7950..2f9df09408 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -18,26 +18,16 @@ import { Logger } from 'winston'; import fs from 'fs'; import path from 'path'; import { GraphQLModule } from '@graphql-modules/core'; -import { Resolvers, CatalogEntity, CatalogQuery } from './types'; -import { Entity } from '@backstage/catalog-model'; +import { Resolvers, CatalogQuery } from './types'; import { Config } from '@backstage/config'; import { CatalogClient } from '../service/client'; +import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json'; export interface ModuleOptions { logger: Logger; config: Config; } -const parseToCatalogEntities = (e: Entity): CatalogEntity => ({ - ...e, - metadata: { - ...e.metadata, - annotations: Object.entries( - e.metadata.annotations ?? {}, - ).map(([key, value]) => ({ key, value })), - }, -}); - export async function createModule( options: ModuleOptions, ): Promise { @@ -51,18 +41,19 @@ export async function createModule( ); const resolvers: Resolvers = { + JSON: GraphQLJSON, + JSONObject: GraphQLJSONObject, Query: { catalog: () => ({} as CatalogQuery), }, CatalogQuery: { list: async () => { - const list = await catalogClient.list(); - return list.map(parseToCatalogEntities); + return await catalogClient.list(); }, }, EntityMetadata: { - annotation: (e, { name }) => - e.annotations?.find(a => a.key === name) ?? null, + annotations: e => e.annotations, + annotation: (e, { name }) => e.annotations[name] ?? null, }, }; diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql index b28f796855..5b2ee51db0 100644 --- a/plugins/catalog-graphql/src/schema.gql +++ b/plugins/catalog-graphql/src/schema.gql @@ -1,3 +1,6 @@ +scalar JSON +scalar JSONObject + type EntityMetadataAnnotation { key: String! value: String! @@ -6,8 +9,7 @@ type EntityMetadataAnnotation { type EntityMetadata { name: String! namespace: String - annotation(name: String): EntityMetadataAnnotation - annotations: [EntityMetadataAnnotation!] + annotations: JSONObject } type CatalogEntity { diff --git a/yarn.lock b/yarn.lock index fe0a3374e6..2d3205f23a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11997,6 +11997,11 @@ graphql-tools@^4.0.0: iterall "^1.1.3" uuid "^3.1.0" +graphql-type-json@^0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115" + integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg== + graphql-upload@^8.0.2: version "8.1.0" resolved "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.1.0.tgz#6d0ab662db5677a68bfb1f2c870ab2544c14939a" @@ -14399,6 +14404,25 @@ jsonpointer@^4.0.1: resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= +<<<<<<< HEAD +======= +jsonwebtoken@^8.1.0: + version "8.5.1" + resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +>>>>>>> feat(gql/catlaog): Tidying up the resolver for the annotations jspdf-autotable@3.5.3: version "3.5.3" resolved "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.5.3.tgz#2f73adb07f340e7dbf22950e3e6c8bf853991479" @@ -17102,7 +17126,11 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +<<<<<<< HEAD parse-filepath@^1.0.1: +======= +parse-filepath@1.0.2, parse-filepath@^1.0.1: +>>>>>>> feat(gql/catlaog): Tidying up the resolver for the annotations version "1.0.2" resolved "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= @@ -18184,12 +18212,43 @@ pretty-hrtime@^1.0.3: resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= +<<<<<<< HEAD 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== optionalDependencies: clipboard "^2.0.0" +======= +prisma-json-schema@0.1.3: + version "0.1.3" + resolved "https://registry.npmjs.org/prisma-json-schema/-/prisma-json-schema-0.1.3.tgz#6c302db8f464f8b92e8694d3f7dd3f41ac9afcbe" + integrity sha512-XZrf2080oR81mY8/OC8al68HiwBm0nXlFE727JIia0ZbNqwuV4MyRYk6E0+OIa6/9KEYxZrcAmoBs3EW1cCvnA== + +prisma-yml@1.34.10: + version "1.34.10" + resolved "https://registry.npmjs.org/prisma-yml/-/prisma-yml-1.34.10.tgz#0ef1ad3a125f54f200289cba56bdd9597f17f410" + integrity sha512-N9on+Cf/XQKFGUULk/681tnpfqiZ19UBTurFMm+/9rnml37mteDaFr2k8yz+K8Gt2xpEJ7kBu7ikG5PrXI1uoA== + dependencies: + ajv "5" + bluebird "^3.5.1" + chalk "^2.3.0" + debug "^3.1.0" + dotenv "^4.0.0" + fs-extra "^7.0.0" + graphql-request "^1.5.0" + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" + isomorphic-fetch "^2.2.1" + js-yaml "^3.10.0" + json-stable-stringify "^1.0.1" + jsonwebtoken "^8.1.0" + lodash "^4.17.4" + prisma-json-schema "0.1.3" + replaceall "^0.1.6" + scuid "^1.0.2" + yaml-ast-parser "^0.0.40" +>>>>>>> feat(gql/catlaog): Tidying up the resolver for the annotations prismjs@^1.20.0, prismjs@^1.8.4, prismjs@~1.20.0: version "1.20.0" @@ -19606,7 +19665,11 @@ request-promise-native@^1.0.5, request-promise-native@^1.0.8: stealthy-require "^1.1.1" tough-cookie "^2.3.3" +<<<<<<< HEAD request@^2.53.0, request@^2.55.0, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +======= +request@2.88.2, request@^2.55.0, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +>>>>>>> feat(gql/catlaog): Tidying up the resolver for the annotations version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== From 69ed3dbe3a68f436fa181c29b5bcac81cec75754 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:00:55 +0200 Subject: [PATCH 16/34] chore(gql/catalog): Added some more metadata fields to the catalog entities --- plugins/catalog-graphql/src/schema.gql | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql index 5b2ee51db0..02abffa09e 100644 --- a/plugins/catalog-graphql/src/schema.gql +++ b/plugins/catalog-graphql/src/schema.gql @@ -8,14 +8,19 @@ type EntityMetadataAnnotation { type EntityMetadata { name: String! - namespace: String + namespace: String! annotations: JSONObject + annotation(name: String): JSON + labels: JSONObject + uid: String! + etag: String! + generation: Int! } type CatalogEntity { - apiVersion: String + apiVersion: String! kind: String! - metadata: EntityMetadata + metadata: EntityMetadata! } type CatalogQuery { From cda9e0b3a0c8678df95d1f9f8a35ae04dd2660be Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:09:30 +0200 Subject: [PATCH 17/34] chore(gql/catalog): making the client alittle more readabke --- plugins/catalog-graphql/src/service/client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts index eddae2f396..f7bc5ccffe 100644 --- a/plugins/catalog-graphql/src/service/client.ts +++ b/plugins/catalog-graphql/src/service/client.ts @@ -27,6 +27,7 @@ export class CatalogClient { throw new Error(`NOPE, ${await res.text()}`); } - return await (res.json() as Promise); + const entities: Entity[] = await res.json(); + return entities; } } From 512b7a2451c6097595fb1c4d4ba963b305408483 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:37:30 +0200 Subject: [PATCH 18/34] feat(gql/catalog): Fixing some of the query issues --- plugins/catalog-graphql/src/graphql/module.ts | 10 ++- plugins/catalog-graphql/src/graphql/types.ts | 74 ++++++++++++------- plugins/catalog-graphql/src/schema.gql | 12 +-- plugins/catalog-graphql/src/service/client.ts | 18 ++++- 4 files changed, 74 insertions(+), 40 deletions(-) diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 2f9df09408..6deb2a3aad 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -48,7 +48,15 @@ export async function createModule( }, CatalogQuery: { list: async () => { - return await catalogClient.list(); + const catalogEntries = await catalogClient.list(); + return catalogEntries.map(entity => ({ + ...entity, + metadata: { + ...entity.metadata, + annotations: entity.metadata.annotations ?? {}, + labels: entity.metadata.labels ?? {}, + }, + })); }, }, EntityMetadata: { diff --git a/plugins/catalog-graphql/src/graphql/types.ts b/plugins/catalog-graphql/src/graphql/types.ts index 567e19c8bb..7294f749ad 100644 --- a/plugins/catalog-graphql/src/graphql/types.ts +++ b/plugins/catalog-graphql/src/graphql/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { GraphQLResolveInfo } from 'graphql'; +import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; import { ModuleContext } from '@graphql-modules/core'; export type Maybe = T | null; @@ -26,32 +26,40 @@ export type Scalars = { Boolean: boolean; Int: number; Float: number; + JSON: any; + JSONObject: any; }; -export type EntityMetadataAnnotation = { - __typename?: 'EntityMetadataAnnotation'; - key: Scalars['String']; - value: Scalars['String']; -}; + export type EntityMetadata = { __typename?: 'EntityMetadata'; name: Scalars['String']; - namespace?: Maybe; - annotation?: Maybe; - annotations?: Maybe>; + namespace: Scalars['String']; + annotations: Scalars['JSONObject']; + annotation?: Maybe; + labels: Scalars['JSONObject']; + label?: Maybe; + uid: Scalars['String']; + etag: Scalars['String']; + generation: Scalars['Int']; }; export type EntityMetadataAnnotationArgs = { - name?: Maybe; + name: Scalars['String']; +}; + + +export type EntityMetadataLabelArgs = { + name: Scalars['String']; }; export type CatalogEntity = { __typename?: 'CatalogEntity'; - apiVersion?: Maybe; + apiVersion: Scalars['String']; kind: Scalars['String']; - metadata?: Maybe; + metadata: EntityMetadata; }; export type CatalogQuery = { @@ -143,9 +151,11 @@ export type DirectiveResolverFn>; - String: ResolverTypeWrapper>; + JSON: ResolverTypeWrapper>; + JSONObject: ResolverTypeWrapper>; EntityMetadata: ResolverTypeWrapper>; + String: ResolverTypeWrapper>; + Int: ResolverTypeWrapper>; CatalogEntity: ResolverTypeWrapper>; CatalogQuery: ResolverTypeWrapper>; Query: ResolverTypeWrapper<{}>; @@ -154,33 +164,42 @@ export type ResolversTypes = ResolversObject<{ /** Mapping between all available schema types and the resolvers parents */ export type ResolversParentTypes = ResolversObject<{ - EntityMetadataAnnotation: Partial; - String: Partial; + JSON: Partial; + JSONObject: Partial; EntityMetadata: Partial; + String: Partial; + Int: Partial; CatalogEntity: Partial; CatalogQuery: Partial; Query: {}; Boolean: Partial; }>; -export type EntityMetadataAnnotationResolvers = ResolversObject<{ - key?: Resolver; - value?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; +export interface JsonScalarConfig extends GraphQLScalarTypeConfig { + name: 'JSON'; +} + +export interface JsonObjectScalarConfig extends GraphQLScalarTypeConfig { + name: 'JSONObject'; +} export type EntityMetadataResolvers = ResolversObject<{ name?: Resolver; - namespace?: Resolver, ParentType, ContextType>; - annotation?: Resolver, ParentType, ContextType, RequireFields>; - annotations?: Resolver>, ParentType, ContextType>; + namespace?: Resolver; + annotations?: Resolver; + annotation?: Resolver, ParentType, ContextType, RequireFields>; + labels?: Resolver; + label?: Resolver, ParentType, ContextType, RequireFields>; + uid?: Resolver; + etag?: Resolver; + generation?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; export type CatalogEntityResolvers = ResolversObject<{ - apiVersion?: Resolver, ParentType, ContextType>; + apiVersion?: Resolver; kind?: Resolver; - metadata?: Resolver, ParentType, ContextType>; + metadata?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -194,7 +213,8 @@ export type QueryResolvers; export type Resolvers = ResolversObject<{ - EntityMetadataAnnotation?: EntityMetadataAnnotationResolvers; + JSON?: GraphQLScalarType; + JSONObject?: GraphQLScalarType; EntityMetadata?: EntityMetadataResolvers; CatalogEntity?: CatalogEntityResolvers; CatalogQuery?: CatalogQueryResolvers; diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql index 02abffa09e..bb8e5bcc87 100644 --- a/plugins/catalog-graphql/src/schema.gql +++ b/plugins/catalog-graphql/src/schema.gql @@ -1,17 +1,13 @@ scalar JSON scalar JSONObject -type EntityMetadataAnnotation { - key: String! - value: String! -} - type EntityMetadata { name: String! namespace: String! - annotations: JSONObject - annotation(name: String): JSON - labels: JSONObject + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON uid: String! etag: String! generation: Int! diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts index f7bc5ccffe..05e73b88b8 100644 --- a/plugins/catalog-graphql/src/service/client.ts +++ b/plugins/catalog-graphql/src/service/client.ts @@ -14,20 +14,30 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, EntityMeta } from '@backstage/catalog-model'; import fetch from 'node-fetch'; +import { JsonObject } from '@backstage/config'; +export interface ReaderEntityMeta extends EntityMeta { + uid: string; + etag: string; + generation: number; + namespace: string; +} +export interface ReaderEntity extends Entity { + metadata: JsonObject & ReaderEntityMeta; +} export class CatalogClient { constructor(private baseUrl: string) {} - async list(): Promise { + async list(): Promise { const res = await fetch(`${this.baseUrl}/catalog/entities`); if (!res.ok) { // todo(blam): need some better way to handle errors here // experiment with throwing the input errors etc and having graphql versions of that - throw new Error(`NOPE, ${await res.text()}`); + throw new Error(await res.text()); } - const entities: Entity[] = await res.json(); + const entities: ReaderEntity[] = await res.json(); return entities; } } From aacaabb6f597b8177c4ffb00c74a7ac439efdab7 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:42:07 +0200 Subject: [PATCH 19/34] feat(gql/catalog): implement the label resolver --- plugins/catalog-graphql/src/graphql/module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 6deb2a3aad..772e472d1b 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -60,8 +60,8 @@ export async function createModule( }, }, EntityMetadata: { - annotations: e => e.annotations, - annotation: (e, { name }) => e.annotations[name] ?? null, + annotation: (e, { name }) => e.annotations?.[name] ?? null, + label: (e, { name }) => e.labels?.[name] ?? null, }, }; From 68e56412765b721b8c8c1dc49f00bd9a1d5c7900 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:43:44 +0200 Subject: [PATCH 20/34] chore(gql/catalog): removing the mock-data stuff --- plugins/graphql/package.json | 3 +-- plugins/graphql/scripts/mock-data.sh | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100755 plugins/graphql/scripts/mock-data.sh diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 548a4c407f..b0ccb86d36 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -16,8 +16,7 @@ "test": "backstage-cli test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean", - "mock-data": "./scripts/mock-data.sh" + "clean": "backstage-cli clean" }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.18", diff --git a/plugins/graphql/scripts/mock-data.sh b/plugins/graphql/scripts/mock-data.sh deleted file mode 100755 index ff30921715..0000000000 --- a/plugins/graphql/scripts/mock-data.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo "use this script to load your service with some mock data if needed!" From 2f3b7410640d461bc59d8c3929fe2e499d81b82a Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:46:54 +0200 Subject: [PATCH 21/34] chore(gql/catalog): fixing bundling of graphql asse --- plugins/catalog-graphql/package.json | 3 ++- plugins/catalog-graphql/src/graphql/module.ts | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index be5c585186..d339119901 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -44,6 +44,7 @@ "supertest": "^4.0.2" }, "files": [ - "dist" + "dist", + "schema.gql" ] } diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 772e472d1b..eb4c550819 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -28,13 +28,15 @@ export interface ModuleOptions { config: Config; } +const schemaPath = path.resolve( + require.resolve('@backstage/plugin-graphql-backend/package.json'), + '../schema.gql', +); + export async function createModule( options: ModuleOptions, ): Promise { - const typeDefs = await fs.promises.readFile( - path.resolve(__dirname, '..', 'schema.gql'), - 'utf-8', - ); + const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); const catalogClient = new CatalogClient( options.config.getString('backend.baseUrl'), From 1869563adea9d57feb14d9a55cd89e51608ed3d7 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:47:31 +0200 Subject: [PATCH 22/34] chore(gql/catalog): actually read --- plugins/catalog-graphql/src/graphql/module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index eb4c550819..967df97662 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -29,7 +29,7 @@ export interface ModuleOptions { } const schemaPath = path.resolve( - require.resolve('@backstage/plugin-graphql-backend/package.json'), + require.resolve('@backstage/plugin-catalog-graphql/package.json'), '../schema.gql', ); From 8837f802022fdc48a1ccd8eda4f093343b91ac7e Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:52:05 +0200 Subject: [PATCH 23/34] chore(gql/catalog): removing the mock data scripts for catalog-graph l --- plugins/catalog-graphql/package.json | 3 +-- plugins/catalog-graphql/scripts/mock-data.sh | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100755 plugins/catalog-graphql/scripts/mock-data.sh diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index d339119901..28e72cefd3 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -17,8 +17,7 @@ "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", "generate:types": "graphql-codegen", - "clean": "backstage-cli clean", - "mock-data": "./scripts/mock-data.sh" + "clean": "backstage-cli clean" }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.18", diff --git a/plugins/catalog-graphql/scripts/mock-data.sh b/plugins/catalog-graphql/scripts/mock-data.sh deleted file mode 100755 index ff30921715..0000000000 --- a/plugins/catalog-graphql/scripts/mock-data.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo "use this script to load your service with some mock data if needed!" From ad0efc8ee0dc844e2e92e4eeaefee62be86b7062 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 14:21:10 +0200 Subject: [PATCH 24/34] chore(gql/catalog): updating the return types from the catalog api --- plugins/catalog-graphql/src/graphql/module.ts | 10 +--------- plugins/catalog-graphql/src/service/client.ts | 2 ++ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 967df97662..3068b3b129 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -50,15 +50,7 @@ export async function createModule( }, CatalogQuery: { list: async () => { - const catalogEntries = await catalogClient.list(); - return catalogEntries.map(entity => ({ - ...entity, - metadata: { - ...entity.metadata, - annotations: entity.metadata.annotations ?? {}, - labels: entity.metadata.labels ?? {}, - }, - })); + return await catalogClient.list(); }, }, EntityMetadata: { diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts index 05e73b88b8..8af97a89b1 100644 --- a/plugins/catalog-graphql/src/service/client.ts +++ b/plugins/catalog-graphql/src/service/client.ts @@ -23,6 +23,8 @@ export interface ReaderEntityMeta extends EntityMeta { etag: string; generation: number; namespace: string; + annotations: Record; + labels: Record; } export interface ReaderEntity extends Entity { metadata: JsonObject & ReaderEntityMeta; From fed0af128bccc419cfda6a890310e1640e7ecd1c Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 16:11:21 +0200 Subject: [PATCH 25/34] chore(catalog/gql): move the catalog schema out of src --- plugins/catalog-graphql/{src => }/schema.gql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/catalog-graphql/{src => }/schema.gql (100%) diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/schema.gql similarity index 100% rename from plugins/catalog-graphql/src/schema.gql rename to plugins/catalog-graphql/schema.gql From ae2a42063dae3c1c59173e449711ef1c11f152b8 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 16:12:15 +0200 Subject: [PATCH 26/34] chore(catalog/gql): fixing csp for playground --- plugins/graphql/package.json | 1 + plugins/graphql/src/service/router.ts | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index b0ccb86d36..d50043d50f 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -29,6 +29,7 @@ "express": "^4.17.1", "express-promise-router": "^3.0.3", "graphql": "^15.3.0", + "helmet": "^4.0.0", "node-fetch": "^2.6.0", "reflect-metadata": "^0.1.13", "winston": "^3.2.1", diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts index 784b801b86..cd926da3a2 100644 --- a/plugins/graphql/src/service/router.ts +++ b/plugins/graphql/src/service/router.ts @@ -24,6 +24,7 @@ import { GraphQLModule } from '@graphql-modules/core'; import { ApolloServer } from 'apollo-server-express'; import { createModule as createCatalogModule } from '@backstage/plugin-catalog-graphql'; import { Config } from '@backstage/config'; +import helmet from 'helmet'; const schemaPath = path.resolve( require.resolve('@backstage/plugin-graphql-backend/package.json'), @@ -41,15 +42,29 @@ export async function createRouter( const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); const catalogModule = await createCatalogModule(options); + const { schema } = new GraphQLModule({ imports: [catalogModule], typeDefs, }); - const server = new ApolloServer({ schema, logger: options.logger }); + const server = new ApolloServer({ + schema, + logger: options.logger, + }); + const router = Router(); const apolloMiddlware = server.getMiddleware({ path: '/' }); + + router.use( + helmet.contentSecurityPolicy({ + directives: { + defaultSrc: ["'self'", "'unsafe-inline'", 'http://*'], + }, + }), + ); + router.use(apolloMiddlware); router.get('/health', (_, response) => { From 7ea09aba1ed29b79058dbc5c706ef9842e3f7311 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 18:50:08 +0200 Subject: [PATCH 27/34] feat(gql/catalog): Reduce the amount of duplication a little for unions --- plugins/catalog-graphql/codegen.yml | 2 +- plugins/catalog-graphql/schema.gql | 38 +++++++++- plugins/catalog-graphql/src/graphql/module.ts | 54 +++++++++++++- plugins/catalog-graphql/src/graphql/types.ts | 72 +++++++++++++++++-- 4 files changed, 156 insertions(+), 10 deletions(-) diff --git a/plugins/catalog-graphql/codegen.yml b/plugins/catalog-graphql/codegen.yml index ebdb34a938..a4f3abcbe4 100644 --- a/plugins/catalog-graphql/codegen.yml +++ b/plugins/catalog-graphql/codegen.yml @@ -1,5 +1,5 @@ overwrite: true -schema: ./src/schema.gql +schema: ./schema.gql require: - ts-node/register/transpile-only generates: diff --git a/plugins/catalog-graphql/schema.gql b/plugins/catalog-graphql/schema.gql index bb8e5bcc87..813b2840a6 100644 --- a/plugins/catalog-graphql/schema.gql +++ b/plugins/catalog-graphql/schema.gql @@ -1,9 +1,43 @@ scalar JSON scalar JSONObject -type EntityMetadata { +interface EntityMetadata { + name: String! + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON + uid: String! + etag: String! + generation: Int! +} + +type ComponentMetadata implements EntityMetadata { + name: String! + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON + uid: String! + etag: String! + generation: Int! + relationships: String! +} + +type TemplateMetadata implements EntityMetadata { + name: String! + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON + uid: String! + etag: String! + generation: Int! + updatedBy: String! +} + +type DefaultEntityMetadata implements EntityMetadata { name: String! - namespace: String! annotations: JSONObject! annotation(name: String!): JSON labels: JSONObject! diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 3068b3b129..f66ff5073e 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -17,8 +17,14 @@ import { Logger } from 'winston'; import fs from 'fs'; import path from 'path'; +import { makeExecutableSchema } from 'apollo-server'; import { GraphQLModule } from '@graphql-modules/core'; -import { Resolvers, CatalogQuery } from './types'; +import { + Resolvers, + CatalogQuery, + CatalogEntityTypes, + EntityMetadataResolvers, +} from './types'; import { Config } from '@backstage/config'; import { CatalogClient } from '../service/client'; import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json'; @@ -33,6 +39,19 @@ const schemaPath = path.resolve( '../schema.gql', ); +const getSpecTypenameForEntity = (e: { kind?: string }) => { + switch (e.kind) { + case 'Component': + return 'ComponentEntity'; + case 'Location': + return 'LocationEntity'; + case 'Template': + return 'TemplateEntity'; + default: + return null; + } +}; + export async function createModule( options: ModuleOptions, ): Promise { @@ -53,15 +72,46 @@ export async function createModule( return await catalogClient.list(); }, }, + ComponentMetadata: { + relationships: () => 'boop', + }, + TemplateMetadata: { + updatedBy: () => 'blam', + }, + CatalogEntity: { + metadata: e => ({ ...e.metadata!, catalogEntity: e }), + }, EntityMetadata: { + __resolveType: (obj: any) => { + const kind = obj.catalogEntity!.kind; + switch (kind) { + case 'Component': + return 'ComponentMetadata'; + case 'Template': + return 'TemplateMetadata'; + default: + return 'DefaultEntityMetadata'; + } + }, annotation: (e, { name }) => e.annotations?.[name] ?? null, + labels: e => e ?? {}, + annotations: e => e ?? {}, label: (e, { name }) => e.labels?.[name] ?? null, }, }; - const module = new GraphQLModule({ + const schema = makeExecutableSchema({ typeDefs, resolvers, + inheritResolversFromInterfaces: true, + resolverValidationOptions: { + allowResolversNotInSchema: true, + }, + }); + + const module = new GraphQLModule({ + resolverValidationOptions: {}, + extraSchemas: [schema], logger: options.logger as any, }); diff --git a/plugins/catalog-graphql/src/graphql/types.ts b/plugins/catalog-graphql/src/graphql/types.ts index 7294f749ad..e2b10ec6a2 100644 --- a/plugins/catalog-graphql/src/graphql/types.ts +++ b/plugins/catalog-graphql/src/graphql/types.ts @@ -33,9 +33,7 @@ export type Scalars = { export type EntityMetadata = { - __typename?: 'EntityMetadata'; name: Scalars['String']; - namespace: Scalars['String']; annotations: Scalars['JSONObject']; annotation?: Maybe; labels: Scalars['JSONObject']; @@ -55,6 +53,39 @@ export type EntityMetadataLabelArgs = { name: Scalars['String']; }; +export type ComponentMetadata = EntityMetadata & { + __typename?: 'ComponentMetadata'; + relationships: Scalars['String']; +}; + +export type TemplateMetadata = EntityMetadata & { + __typename?: 'TemplateMetadata'; + updatedBy: Scalars['String']; +}; + +export type DefaultEntityMetadata = EntityMetadata & { + __typename?: 'DefaultEntityMetadata'; + name: Scalars['String']; + annotations: Scalars['JSONObject']; + annotation?: Maybe; + labels: Scalars['JSONObject']; + label?: Maybe; + uid: Scalars['String']; + etag: Scalars['String']; + generation: Scalars['Int']; + updatedBy: Scalars['String']; +}; + + +export type DefaultEntityMetadataAnnotationArgs = { + name: Scalars['String']; +}; + + +export type DefaultEntityMetadataLabelArgs = { + name: Scalars['String']; +}; + export type CatalogEntity = { __typename?: 'CatalogEntity'; apiVersion: Scalars['String']; @@ -153,9 +184,12 @@ export type DirectiveResolverFn>; JSONObject: ResolverTypeWrapper>; - EntityMetadata: ResolverTypeWrapper>; + EntityMetadata: ResolversTypes['ComponentMetadata'] | ResolversTypes['TemplateMetadata'] | ResolversTypes['DefaultEntityMetadata']; String: ResolverTypeWrapper>; Int: ResolverTypeWrapper>; + ComponentMetadata: ResolverTypeWrapper>; + TemplateMetadata: ResolverTypeWrapper>; + DefaultEntityMetadata: ResolverTypeWrapper>; CatalogEntity: ResolverTypeWrapper>; CatalogQuery: ResolverTypeWrapper>; Query: ResolverTypeWrapper<{}>; @@ -166,9 +200,12 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ JSON: Partial; JSONObject: Partial; - EntityMetadata: Partial; + EntityMetadata: ResolversParentTypes['ComponentMetadata'] | ResolversParentTypes['TemplateMetadata'] | ResolversParentTypes['DefaultEntityMetadata']; String: Partial; Int: Partial; + ComponentMetadata: Partial; + TemplateMetadata: Partial; + DefaultEntityMetadata: Partial; CatalogEntity: Partial; CatalogQuery: Partial; Query: {}; @@ -184,8 +221,8 @@ export interface JsonObjectScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ + __resolveType: TypeResolveFn<'ComponentMetadata' | 'TemplateMetadata' | 'DefaultEntityMetadata', ParentType, ContextType>; name?: Resolver; - namespace?: Resolver; annotations?: Resolver; annotation?: Resolver, ParentType, ContextType, RequireFields>; labels?: Resolver; @@ -193,6 +230,28 @@ export type EntityMetadataResolvers; etag?: Resolver; generation?: Resolver; +}>; + +export type ComponentMetadataResolvers = ResolversObject<{ + relationships?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type TemplateMetadataResolvers = ResolversObject<{ + updatedBy?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DefaultEntityMetadataResolvers = ResolversObject<{ + name?: Resolver; + annotations?: Resolver; + annotation?: Resolver, ParentType, ContextType, RequireFields>; + labels?: Resolver; + label?: Resolver, ParentType, ContextType, RequireFields>; + uid?: Resolver; + etag?: Resolver; + generation?: Resolver; + updatedBy?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -216,6 +275,9 @@ export type Resolvers = ResolversObject<{ JSON?: GraphQLScalarType; JSONObject?: GraphQLScalarType; EntityMetadata?: EntityMetadataResolvers; + ComponentMetadata?: ComponentMetadataResolvers; + TemplateMetadata?: TemplateMetadataResolvers; + DefaultEntityMetadata?: DefaultEntityMetadataResolvers; CatalogEntity?: CatalogEntityResolvers; CatalogQuery?: CatalogQueryResolvers; Query?: QueryResolvers; From 3f601b60558cf6bd652c66d9729f8f5f06935925 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 12 Aug 2020 01:45:47 +0200 Subject: [PATCH 28/34] feat(gql/catalog): working on making the schema nice --- plugins/catalog-graphql/codegen.yml | 7 +- plugins/catalog-graphql/package.json | 2 + plugins/catalog-graphql/schema.gql | 62 --- plugins/catalog-graphql/src/graphql/module.ts | 75 ++- plugins/catalog-graphql/src/graphql/types.ts | 434 +++++++++++++++--- 5 files changed, 403 insertions(+), 177 deletions(-) delete mode 100644 plugins/catalog-graphql/schema.gql diff --git a/plugins/catalog-graphql/codegen.yml b/plugins/catalog-graphql/codegen.yml index a4f3abcbe4..bb50b68101 100644 --- a/plugins/catalog-graphql/codegen.yml +++ b/plugins/catalog-graphql/codegen.yml @@ -1,9 +1,7 @@ overwrite: true -schema: ./schema.gql -require: - - ts-node/register/transpile-only generates: ./src/graphql/types.ts: + schema: ./src/schema.js plugins: - typescript - typescript-resolvers @@ -11,6 +9,7 @@ generates: afterOneFileWrite: - eslint --fix config: - contextType: '@graphql-modules/core#ModuleContext' + contextType: "@graphql-modules/core#ModuleContext" + allowParentTypeOverride: true useIndexSignature: true defaultMapper: Partial<{T}> diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 28e72cefd3..83e88e1d25 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -27,7 +27,9 @@ "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", + "apollo-server": "^2.16.1", "graphql": "^15.3.0", + "graphql-tag": "^2.11.0", "graphql-type-json": "^0.3.2", "node-fetch": "^2.6.0", "ts-node": "^8.10.2", diff --git a/plugins/catalog-graphql/schema.gql b/plugins/catalog-graphql/schema.gql deleted file mode 100644 index 813b2840a6..0000000000 --- a/plugins/catalog-graphql/schema.gql +++ /dev/null @@ -1,62 +0,0 @@ -scalar JSON -scalar JSONObject - -interface EntityMetadata { - name: String! - annotations: JSONObject! - annotation(name: String!): JSON - labels: JSONObject! - label(name: String!): JSON - uid: String! - etag: String! - generation: Int! -} - -type ComponentMetadata implements EntityMetadata { - name: String! - annotations: JSONObject! - annotation(name: String!): JSON - labels: JSONObject! - label(name: String!): JSON - uid: String! - etag: String! - generation: Int! - relationships: String! -} - -type TemplateMetadata implements EntityMetadata { - name: String! - annotations: JSONObject! - annotation(name: String!): JSON - labels: JSONObject! - label(name: String!): JSON - uid: String! - etag: String! - generation: Int! - updatedBy: String! -} - -type DefaultEntityMetadata implements EntityMetadata { - name: String! - annotations: JSONObject! - annotation(name: String!): JSON - labels: JSONObject! - label(name: String!): JSON - uid: String! - etag: String! - generation: Int! -} - -type CatalogEntity { - apiVersion: String! - kind: String! - metadata: EntityMetadata! -} - -type CatalogQuery { - list: [CatalogEntity!]! -} - -type Query { - catalog: CatalogQuery! -} diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index f66ff5073e..c2266001b9 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -15,47 +15,23 @@ */ import { Logger } from 'winston'; -import fs from 'fs'; -import path from 'path'; import { makeExecutableSchema } from 'apollo-server'; import { GraphQLModule } from '@graphql-modules/core'; -import { - Resolvers, - CatalogQuery, - CatalogEntityTypes, - EntityMetadataResolvers, -} from './types'; +import { Resolvers, CatalogQuery } from './types'; import { Config } from '@backstage/config'; import { CatalogClient } from '../service/client'; import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json'; +import { Entity } from '@backstage/catalog-model'; export interface ModuleOptions { logger: Logger; config: Config; } -const schemaPath = path.resolve( - require.resolve('@backstage/plugin-catalog-graphql/package.json'), - '../schema.gql', -); - -const getSpecTypenameForEntity = (e: { kind?: string }) => { - switch (e.kind) { - case 'Component': - return 'ComponentEntity'; - case 'Location': - return 'LocationEntity'; - case 'Template': - return 'TemplateEntity'; - default: - return null; - } -}; - export async function createModule( options: ModuleOptions, ): Promise { - const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); + const typeDefs = require('../schema'); const catalogClient = new CatalogClient( options.config.getString('backend.baseUrl'), @@ -64,6 +40,12 @@ export async function createModule( const resolvers: Resolvers = { JSON: GraphQLJSON, JSONObject: GraphQLJSONObject, + DefaultEntitySpec: { + raw: rootValue => { + const { entity } = rootValue as { entity: Entity }; + return entity.spec ?? null; + }, + }, Query: { catalog: () => ({} as CatalogQuery), }, @@ -72,18 +54,15 @@ export async function createModule( return await catalogClient.list(); }, }, - ComponentMetadata: { - relationships: () => 'boop', - }, - TemplateMetadata: { - updatedBy: () => 'blam', - }, CatalogEntity: { - metadata: e => ({ ...e.metadata!, catalogEntity: e }), + metadata: entity => ({ ...entity.metadata!, entity }), + spec: entity => ({ ...entity.spec!, entity }), }, EntityMetadata: { - __resolveType: (obj: any) => { - const kind = obj.catalogEntity!.kind; + __resolveType: rootValue => { + const { + entity: { kind }, + } = rootValue as { entity: Entity }; switch (kind) { case 'Component': return 'ComponentMetadata'; @@ -94,23 +73,35 @@ export async function createModule( } }, annotation: (e, { name }) => e.annotations?.[name] ?? null, - labels: e => e ?? {}, - annotations: e => e ?? {}, + labels: e => e.labels ?? {}, + annotations: e => e.annotations ?? {}, label: (e, { name }) => e.labels?.[name] ?? null, }, + EntitySpec: { + __resolveType: rootValue => { + const { + entity: { kind }, + } = rootValue as { entity: Entity }; + + switch (kind) { + case 'Component': + return 'ComponentEntitySpec'; + case 'Template': + return 'TemplateEntitySpec'; + default: + return 'DefaultEntitySpec'; + } + }, + }, }; const schema = makeExecutableSchema({ typeDefs, resolvers, inheritResolversFromInterfaces: true, - resolverValidationOptions: { - allowResolversNotInSchema: true, - }, }); const module = new GraphQLModule({ - resolverValidationOptions: {}, extraSchemas: [schema], logger: options.logger as any, }); diff --git a/plugins/catalog-graphql/src/graphql/types.ts b/plugins/catalog-graphql/src/graphql/types.ts index e2b10ec6a2..446a1f9f46 100644 --- a/plugins/catalog-graphql/src/graphql/types.ts +++ b/plugins/catalog-graphql/src/graphql/types.ts @@ -13,12 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; +import { + GraphQLResolveInfo, + GraphQLScalarType, + GraphQLScalarTypeConfig, +} from 'graphql'; import { ModuleContext } from '@graphql-modules/core'; export type Maybe = T | null; -export type Exact = { [K in keyof T]: T[K] }; -export type RequireFields = { [X in Exclude]?: T[X] } & { [P in K]-?: NonNullable }; +export type Exact = { + [K in keyof T]: T[K]; +}; +export type Omit = Pick>; +export type RequireFields = { + [X in Exclude]?: T[X]; +} & + { [P in K]-?: NonNullable }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { ID: string; @@ -30,8 +40,6 @@ export type Scalars = { JSONObject: any; }; - - export type EntityMetadata = { name: Scalars['String']; annotations: Scalars['JSONObject']; @@ -43,26 +51,14 @@ export type EntityMetadata = { generation: Scalars['Int']; }; - export type EntityMetadataAnnotationArgs = { name: Scalars['String']; }; - export type EntityMetadataLabelArgs = { name: Scalars['String']; }; -export type ComponentMetadata = EntityMetadata & { - __typename?: 'ComponentMetadata'; - relationships: Scalars['String']; -}; - -export type TemplateMetadata = EntityMetadata & { - __typename?: 'TemplateMetadata'; - updatedBy: Scalars['String']; -}; - export type DefaultEntityMetadata = EntityMetadata & { __typename?: 'DefaultEntityMetadata'; name: Scalars['String']; @@ -73,24 +69,97 @@ export type DefaultEntityMetadata = EntityMetadata & { uid: Scalars['String']; etag: Scalars['String']; generation: Scalars['Int']; - updatedBy: Scalars['String']; }; - export type DefaultEntityMetadataAnnotationArgs = { name: Scalars['String']; }; - export type DefaultEntityMetadataLabelArgs = { name: Scalars['String']; }; +export type ComponentMetadata = EntityMetadata & { + __typename?: 'ComponentMetadata'; + name: Scalars['String']; + annotations: Scalars['JSONObject']; + annotation?: Maybe; + labels: Scalars['JSONObject']; + label?: Maybe; + uid: Scalars['String']; + etag: Scalars['String']; + generation: Scalars['Int']; + relationships?: Maybe; +}; + +export type ComponentMetadataAnnotationArgs = { + name: Scalars['String']; +}; + +export type ComponentMetadataLabelArgs = { + name: Scalars['String']; +}; + +export type TemplateMetadata = EntityMetadata & { + __typename?: 'TemplateMetadata'; + name: Scalars['String']; + annotations: Scalars['JSONObject']; + annotation?: Maybe; + labels: Scalars['JSONObject']; + label?: Maybe; + uid: Scalars['String']; + etag: Scalars['String']; + generation: Scalars['Int']; + updatedBy?: Maybe; +}; + +export type TemplateMetadataAnnotationArgs = { + name: Scalars['String']; +}; + +export type TemplateMetadataLabelArgs = { + name: Scalars['String']; +}; + +export type TemplateEntitySpec = { + __typename?: 'TemplateEntitySpec'; + type: Scalars['String']; + path?: Maybe; + schema: Scalars['JSONObject']; + templater: Scalars['String']; +}; + +export type ComponentEntitySpec = { + __typename?: 'ComponentEntitySpec'; + title: Scalars['String']; + lifecycle: Scalars['String']; + owner: Scalars['String']; +}; + +export type LocationEntitySpec = { + __typename?: 'LocationEntitySpec'; + type: Scalars['String']; + target?: Maybe; + targets: Array; +}; + +export type DefaultEntitySpec = { + __typename?: 'DefaultEntitySpec'; + raw?: Maybe; +}; + +export type EntitySpec = + | DefaultEntitySpec + | TemplateEntitySpec + | LocationEntitySpec + | ComponentEntitySpec; + export type CatalogEntity = { __typename?: 'CatalogEntity'; apiVersion: Scalars['String']; kind: Scalars['String']; - metadata: EntityMetadata; + metadata?: Maybe; + spec: EntitySpec; }; export type CatalogQuery = { @@ -108,7 +177,6 @@ export type ResolversObject = WithIndex; export type ResolverTypeWrapper = Promise | T; - export type LegacyStitchingResolver = { fragment: string; resolve: ResolverFn; @@ -118,7 +186,9 @@ export type NewStitchingResolver = { selectionSet: string; resolve: ResolverFn; }; -export type StitchingResolver = LegacyStitchingResolver | NewStitchingResolver; +export type StitchingResolver = + | LegacyStitchingResolver + | NewStitchingResolver; export type Resolver = | ResolverFn | StitchingResolver; @@ -127,26 +197,42 @@ export type ResolverFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => Promise | TResult; export type SubscriptionSubscribeFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => AsyncIterator | Promise>; export type SubscriptionResolveFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => TResult | Promise; -export interface SubscriptionSubscriberObject { - subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; - resolve?: SubscriptionResolveFn; +export interface SubscriptionSubscriberObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs +> { + subscribe: SubscriptionSubscribeFn< + { [key in TKey]: TResult }, + TParent, + TContext, + TArgs + >; + resolve?: SubscriptionResolveFn< + TResult, + { [key in TKey]: TResult }, + TContext, + TArgs + >; } export interface SubscriptionResolverObject { @@ -154,43 +240,82 @@ export interface SubscriptionResolverObject { resolve: SubscriptionResolveFn; } -export type SubscriptionObject = +export type SubscriptionObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs +> = | SubscriptionSubscriberObject | SubscriptionResolverObject; -export type SubscriptionResolver = - | ((...args: any[]) => SubscriptionObject) +export type SubscriptionResolver< + TResult, + TKey extends string, + TParent = {}, + TContext = {}, + TArgs = {} +> = + | (( + ...args: any[] + ) => SubscriptionObject) | SubscriptionObject; export type TypeResolveFn = ( parent: TParent, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => Maybe | Promise>; -export type IsTypeOfResolverFn = (obj: T, info: GraphQLResolveInfo) => boolean | Promise; +export type IsTypeOfResolverFn = ( + obj: T, + info: GraphQLResolveInfo, +) => boolean | Promise; export type NextResolverFn = () => Promise; -export type DirectiveResolverFn = ( +export type DirectiveResolverFn< + TResult = {}, + TParent = {}, + TContext = {}, + TArgs = {} +> = ( next: NextResolverFn, parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => TResult | Promise; /** Mapping between all available schema types and the resolvers types */ export type ResolversTypes = ResolversObject<{ JSON: ResolverTypeWrapper>; JSONObject: ResolverTypeWrapper>; - EntityMetadata: ResolversTypes['ComponentMetadata'] | ResolversTypes['TemplateMetadata'] | ResolversTypes['DefaultEntityMetadata']; + EntityMetadata: + | ResolversTypes['DefaultEntityMetadata'] + | ResolversTypes['ComponentMetadata'] + | ResolversTypes['TemplateMetadata']; String: ResolverTypeWrapper>; Int: ResolverTypeWrapper>; + DefaultEntityMetadata: ResolverTypeWrapper>; ComponentMetadata: ResolverTypeWrapper>; TemplateMetadata: ResolverTypeWrapper>; - DefaultEntityMetadata: ResolverTypeWrapper>; - CatalogEntity: ResolverTypeWrapper>; + TemplateEntitySpec: ResolverTypeWrapper>; + ComponentEntitySpec: ResolverTypeWrapper>; + LocationEntitySpec: ResolverTypeWrapper>; + DefaultEntitySpec: ResolverTypeWrapper>; + EntitySpec: Partial< + | ResolversTypes['DefaultEntitySpec'] + | ResolversTypes['TemplateEntitySpec'] + | ResolversTypes['LocationEntitySpec'] + | ResolversTypes['ComponentEntitySpec'] + >; + CatalogEntity: ResolverTypeWrapper< + Partial< + Omit & { spec: ResolversTypes['EntitySpec'] } + > + >; CatalogQuery: ResolverTypeWrapper>; Query: ResolverTypeWrapper<{}>; Boolean: ResolverTypeWrapper>; @@ -200,74 +325,241 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ JSON: Partial; JSONObject: Partial; - EntityMetadata: ResolversParentTypes['ComponentMetadata'] | ResolversParentTypes['TemplateMetadata'] | ResolversParentTypes['DefaultEntityMetadata']; + EntityMetadata: + | ResolversParentTypes['DefaultEntityMetadata'] + | ResolversParentTypes['ComponentMetadata'] + | ResolversParentTypes['TemplateMetadata']; String: Partial; Int: Partial; + DefaultEntityMetadata: Partial; ComponentMetadata: Partial; TemplateMetadata: Partial; - DefaultEntityMetadata: Partial; - CatalogEntity: Partial; + TemplateEntitySpec: Partial; + ComponentEntitySpec: Partial; + LocationEntitySpec: Partial; + DefaultEntitySpec: Partial; + EntitySpec: Partial< + | ResolversParentTypes['DefaultEntitySpec'] + | ResolversParentTypes['TemplateEntitySpec'] + | ResolversParentTypes['LocationEntitySpec'] + | ResolversParentTypes['ComponentEntitySpec'] + >; + CatalogEntity: Partial< + Omit & { spec: ResolversParentTypes['EntitySpec'] } + >; CatalogQuery: Partial; Query: {}; Boolean: Partial; }>; -export interface JsonScalarConfig extends GraphQLScalarTypeConfig { +export interface JsonScalarConfig + extends GraphQLScalarTypeConfig { name: 'JSON'; } -export interface JsonObjectScalarConfig extends GraphQLScalarTypeConfig { +export interface JsonObjectScalarConfig + extends GraphQLScalarTypeConfig { name: 'JSONObject'; } -export type EntityMetadataResolvers = ResolversObject<{ - __resolveType: TypeResolveFn<'ComponentMetadata' | 'TemplateMetadata' | 'DefaultEntityMetadata', ParentType, ContextType>; +export type EntityMetadataResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['EntityMetadata'] +> = ResolversObject<{ + __resolveType: TypeResolveFn< + 'DefaultEntityMetadata' | 'ComponentMetadata' | 'TemplateMetadata', + ParentType, + ContextType + >; name?: Resolver; annotations?: Resolver; - annotation?: Resolver, ParentType, ContextType, RequireFields>; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; labels?: Resolver; - label?: Resolver, ParentType, ContextType, RequireFields>; + label?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; uid?: Resolver; etag?: Resolver; generation?: Resolver; }>; -export type ComponentMetadataResolvers = ResolversObject<{ - relationships?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; - -export type TemplateMetadataResolvers = ResolversObject<{ - updatedBy?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; - -export type DefaultEntityMetadataResolvers = ResolversObject<{ +export type DefaultEntityMetadataResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['DefaultEntityMetadata'] +> = ResolversObject<{ name?: Resolver; annotations?: Resolver; - annotation?: Resolver, ParentType, ContextType, RequireFields>; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; labels?: Resolver; - label?: Resolver, ParentType, ContextType, RequireFields>; + label?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; uid?: Resolver; etag?: Resolver; generation?: Resolver; - updatedBy?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type CatalogEntityResolvers = ResolversObject<{ +export type ComponentMetadataResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['ComponentMetadata'] +> = ResolversObject<{ + name?: Resolver; + annotations?: Resolver; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + labels?: Resolver; + label?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + uid?: Resolver; + etag?: Resolver; + generation?: Resolver; + relationships?: Resolver< + Maybe, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type TemplateMetadataResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['TemplateMetadata'] +> = ResolversObject<{ + name?: Resolver; + annotations?: Resolver; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + labels?: Resolver; + label?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + uid?: Resolver; + etag?: Resolver; + generation?: Resolver; + updatedBy?: Resolver< + Maybe, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type TemplateEntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['TemplateEntitySpec'] +> = ResolversObject<{ + type?: Resolver; + path?: Resolver, ParentType, ContextType>; + schema?: Resolver; + templater?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type ComponentEntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['ComponentEntitySpec'] +> = ResolversObject<{ + title?: Resolver; + lifecycle?: Resolver; + owner?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type LocationEntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['LocationEntitySpec'] +> = ResolversObject<{ + type?: Resolver; + target?: Resolver, ParentType, ContextType>; + targets?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DefaultEntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['DefaultEntitySpec'] +> = ResolversObject<{ + raw?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type EntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['EntitySpec'] +> = ResolversObject<{ + __resolveType: TypeResolveFn< + | 'DefaultEntitySpec' + | 'TemplateEntitySpec' + | 'LocationEntitySpec' + | 'ComponentEntitySpec', + ParentType, + ContextType + >; +}>; + +export type CatalogEntityResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['CatalogEntity'] +> = ResolversObject<{ apiVersion?: Resolver; kind?: Resolver; - metadata?: Resolver; + metadata?: Resolver< + Maybe, + ParentType, + ContextType + >; + spec?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type CatalogQueryResolvers = ResolversObject<{ - list?: Resolver, ParentType, ContextType>; +export type CatalogQueryResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['CatalogQuery'] +> = ResolversObject<{ + list?: Resolver< + Array, + ParentType, + ContextType + >; __isTypeOf?: IsTypeOfResolverFn; }>; -export type QueryResolvers = ResolversObject<{ +export type QueryResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['Query'] +> = ResolversObject<{ catalog?: Resolver; }>; @@ -275,15 +567,19 @@ export type Resolvers = ResolversObject<{ JSON?: GraphQLScalarType; JSONObject?: GraphQLScalarType; EntityMetadata?: EntityMetadataResolvers; + DefaultEntityMetadata?: DefaultEntityMetadataResolvers; ComponentMetadata?: ComponentMetadataResolvers; TemplateMetadata?: TemplateMetadataResolvers; - DefaultEntityMetadata?: DefaultEntityMetadataResolvers; + TemplateEntitySpec?: TemplateEntitySpecResolvers; + ComponentEntitySpec?: ComponentEntitySpecResolvers; + LocationEntitySpec?: LocationEntitySpecResolvers; + DefaultEntitySpec?: DefaultEntitySpecResolvers; + EntitySpec?: EntitySpecResolvers; CatalogEntity?: CatalogEntityResolvers; CatalogQuery?: CatalogQueryResolvers; Query?: QueryResolvers; }>; - /** * @deprecated * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. From 1b99081df480f3e3c46a29b2a027c8c8cb2ea1e0 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 12 Aug 2020 01:47:09 +0200 Subject: [PATCH 29/34] feat(gql/catalog): to remove the duplication in the schema thanks to the limitations on implements, let's create the schema using javascript Trust me, i've tried getting this to work with a typescript file, but it just would not work with the import export keywords which are needed to satisfy typescript config. couldn't get it to transpile anything properly. so thought screw it for now --- packages/backend-common/src/gql.ts | 17 ++++++ packages/backend-common/src/index.ts | 1 + plugins/catalog-graphql/src/schema.js | 87 +++++++++++++++++++++++++++ yarn.lock | 73 +--------------------- 4 files changed, 106 insertions(+), 72 deletions(-) create mode 100644 packages/backend-common/src/gql.ts create mode 100644 plugins/catalog-graphql/src/schema.js diff --git a/packages/backend-common/src/gql.ts b/packages/backend-common/src/gql.ts new file mode 100644 index 0000000000..ca6b9c7f55 --- /dev/null +++ b/packages/backend-common/src/gql.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 const gql = (s: TemplateStringsArray, ...args: any[]) => + s.map((ss, i) => `${ss}${args[i] || ''}`).join(''); diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index f499613851..007e762ca9 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -21,3 +21,4 @@ export * from './logging'; export * from './middleware'; export * from './service'; export * from './hot'; +export * from './gql'; diff --git a/plugins/catalog-graphql/src/schema.js b/plugins/catalog-graphql/src/schema.js new file mode 100644 index 0000000000..473aad97f5 --- /dev/null +++ b/plugins/catalog-graphql/src/schema.js @@ -0,0 +1,87 @@ +/* + * 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. + */ + +const EntityMetadataFields = /* GraphQL */ ` + name: String! + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON + uid: String! + etag: String! + generation: Int! +`; + +const EntityMetadata = /* GraphQL */ ` + scalar JSON + scalar JSONObject + + interface EntityMetadata { + ${EntityMetadataFields} + } + + type DefaultEntityMetadata implements EntityMetadata { + ${EntityMetadataFields} + } + + type ComponentMetadata implements EntityMetadata { + ${EntityMetadataFields} + # mock field to prove extensions working + relationships: String + } + + type TemplateMetadata implements EntityMetadata { + ${EntityMetadataFields} + # mock field to prove extensions working + updatedBy: String + } + + type TemplateEntitySpec { + type: String! + path: String + schema: JSONObject! + templater: String! + } + + type ComponentEntitySpec { + type: String! + lifecycle: String! + owner: String! + } + + type DefaultEntitySpec { + raw: JSONObject + } + + union EntitySpec = DefaultEntitySpec | TemplateEntitySpec | ComponentEntitySpec + + type CatalogEntity { + apiVersion: String! + kind: String! + metadata: EntityMetadata + spec: EntitySpec! + } + + type CatalogQuery { + list: [CatalogEntity!]! + } + + type Query { + catalog: CatalogQuery! + } +`; + +module.exports = EntityMetadata; diff --git a/yarn.lock b/yarn.lock index 2d3205f23a..58084fb145 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4611,7 +4611,7 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express@*", "@types/express@^4.17.6", "@types/express@^4.17.7": +"@types/express@*", "@types/express@4.17.7", "@types/express@^4.17.6": version "4.17.7" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ== @@ -11997,11 +11997,6 @@ graphql-tools@^4.0.0: iterall "^1.1.3" uuid "^3.1.0" -graphql-type-json@^0.3.2: - version "0.3.2" - resolved "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115" - integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg== - graphql-upload@^8.0.2: version "8.1.0" resolved "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.1.0.tgz#6d0ab662db5677a68bfb1f2c870ab2544c14939a" @@ -14404,25 +14399,6 @@ jsonpointer@^4.0.1: resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= -<<<<<<< HEAD -======= -jsonwebtoken@^8.1.0: - version "8.5.1" - resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - ->>>>>>> feat(gql/catlaog): Tidying up the resolver for the annotations jspdf-autotable@3.5.3: version "3.5.3" resolved "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.5.3.tgz#2f73adb07f340e7dbf22950e3e6c8bf853991479" @@ -17126,11 +17102,7 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -<<<<<<< HEAD parse-filepath@^1.0.1: -======= -parse-filepath@1.0.2, parse-filepath@^1.0.1: ->>>>>>> feat(gql/catlaog): Tidying up the resolver for the annotations version "1.0.2" resolved "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= @@ -18212,43 +18184,12 @@ pretty-hrtime@^1.0.3: resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= -<<<<<<< HEAD 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== optionalDependencies: clipboard "^2.0.0" -======= -prisma-json-schema@0.1.3: - version "0.1.3" - resolved "https://registry.npmjs.org/prisma-json-schema/-/prisma-json-schema-0.1.3.tgz#6c302db8f464f8b92e8694d3f7dd3f41ac9afcbe" - integrity sha512-XZrf2080oR81mY8/OC8al68HiwBm0nXlFE727JIia0ZbNqwuV4MyRYk6E0+OIa6/9KEYxZrcAmoBs3EW1cCvnA== - -prisma-yml@1.34.10: - version "1.34.10" - resolved "https://registry.npmjs.org/prisma-yml/-/prisma-yml-1.34.10.tgz#0ef1ad3a125f54f200289cba56bdd9597f17f410" - integrity sha512-N9on+Cf/XQKFGUULk/681tnpfqiZ19UBTurFMm+/9rnml37mteDaFr2k8yz+K8Gt2xpEJ7kBu7ikG5PrXI1uoA== - dependencies: - ajv "5" - bluebird "^3.5.1" - chalk "^2.3.0" - debug "^3.1.0" - dotenv "^4.0.0" - fs-extra "^7.0.0" - graphql-request "^1.5.0" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - isomorphic-fetch "^2.2.1" - js-yaml "^3.10.0" - json-stable-stringify "^1.0.1" - jsonwebtoken "^8.1.0" - lodash "^4.17.4" - prisma-json-schema "0.1.3" - replaceall "^0.1.6" - scuid "^1.0.2" - yaml-ast-parser "^0.0.40" ->>>>>>> feat(gql/catlaog): Tidying up the resolver for the annotations prismjs@^1.20.0, prismjs@^1.8.4, prismjs@~1.20.0: version "1.20.0" @@ -19436,11 +19377,6 @@ redux@^4.0.4, redux@^4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" -reflect-metadata@^0.1.13: - version "0.1.13" - resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" - integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== - refractor@^2.4.1: version "2.10.1" resolved "https://registry.npmjs.org/refractor/-/refractor-2.10.1.tgz#166c32f114ed16fd96190ad21d5193d3afc7d34e" @@ -19665,11 +19601,7 @@ request-promise-native@^1.0.5, request-promise-native@^1.0.8: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -<<<<<<< HEAD request@^2.53.0, request@^2.55.0, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: -======= -request@2.88.2, request@^2.55.0, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: ->>>>>>> feat(gql/catlaog): Tidying up the resolver for the annotations version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -23083,7 +23015,6 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -<<<<<<< HEAD whatwg-url@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-2.0.1.tgz#5396b2043f020ee6f704d9c45ea8519e724de659" @@ -23092,8 +23023,6 @@ whatwg-url@^2.0.1: tr46 "~0.0.3" webidl-conversions "^3.0.0" -======= ->>>>>>> chore(gql): fixing build issues whatwg-url@^6.4.1: version "6.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" From 641b295a0d15a59baccbfa9a9c7bb44b2186510a Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 12 Aug 2020 02:10:46 +0200 Subject: [PATCH 30/34] chore: reverting code --- packages/backend-common/src/gql.ts | 17 ----------------- packages/backend-common/src/index.ts | 1 - 2 files changed, 18 deletions(-) delete mode 100644 packages/backend-common/src/gql.ts diff --git a/packages/backend-common/src/gql.ts b/packages/backend-common/src/gql.ts deleted file mode 100644 index ca6b9c7f55..0000000000 --- a/packages/backend-common/src/gql.ts +++ /dev/null @@ -1,17 +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. - */ -export const gql = (s: TemplateStringsArray, ...args: any[]) => - s.map((ss, i) => `${ss}${args[i] || ''}`).join(''); diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 007e762ca9..f499613851 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -21,4 +21,3 @@ export * from './logging'; export * from './middleware'; export * from './service'; export * from './hot'; -export * from './gql'; From 88269ecd23a57c00d261e839ab406d4dfac1419c Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 14 Aug 2020 14:00:39 +0200 Subject: [PATCH 31/34] chore(gql/catalog): fixing dependencies --- plugins/catalog-graphql/package.json | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 83e88e1d25..79277bdc2b 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -6,7 +6,7 @@ "license": "Apache-2.0", "publishConfig": { "access": "public", - "main": "dist/index.esm.js", + "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, "scripts": { @@ -20,26 +20,25 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.18", "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.18", "@backstage/config": "^0.1.1-alpha.18", - "@graphql-codegen/cli": "^1.17.7", - "@graphql-codegen/typescript": "^1.17.7", - "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", "apollo-server": "^2.16.1", "graphql": "^15.3.0", "graphql-tag": "^2.11.0", "graphql-type-json": "^0.3.2", "node-fetch": "^2.6.0", - "ts-node": "^8.10.2", - "winston": "^3.2.1", - "yn": "^4.0.0" + "winston": "^3.2.1" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.18", "@types/express": "^4.17.7", "@types/supertest": "^2.0.8", + "@graphql-codegen/cli": "^1.17.7", + "@graphql-codegen/typescript": "^1.17.7", + "@graphql-codegen/typescript-resolvers": "^1.17.7", + "ts-node": "^8.10.2", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.19.5", "supertest": "^4.0.2" From 72d8f54913155875acae339e3c550ed4293ca7ff Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 14 Aug 2020 14:37:17 +0200 Subject: [PATCH 32/34] chore(gql/catalog): fixing some code review comments --- plugins/catalog-graphql/package.json | 3 +-- plugins/graphql/package.json | 4 ++-- plugins/graphql/src/service/router.ts | 17 ++++++++++------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 79277bdc2b..5531c23ade 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -44,7 +44,6 @@ "supertest": "^4.0.2" }, "files": [ - "dist", - "schema.gql" + "dist" ] } diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index d50043d50f..e8b179288c 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -24,8 +24,8 @@ "@backstage/plugin-catalog-graphql": "^0.1.1-alpha.18", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", - "apollo-server": "^2.16.0", - "apollo-server-express": "^2.16.0", + "apollo-server": "^2.16.1", + "apollo-server-express": "^2.16.1", "express": "^4.17.1", "express-promise-router": "^3.0.3", "graphql": "^15.3.0", diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts index cd926da3a2..a06f696533 100644 --- a/plugins/graphql/src/service/router.ts +++ b/plugins/graphql/src/service/router.ts @@ -51,19 +51,22 @@ export async function createRouter( const server = new ApolloServer({ schema, logger: options.logger, + introspection: true, + playground: process.env.NODE_ENV === 'development', }); const router = Router(); const apolloMiddlware = server.getMiddleware({ path: '/' }); - router.use( - helmet.contentSecurityPolicy({ - directives: { - defaultSrc: ["'self'", "'unsafe-inline'", 'http://*'], - }, - }), - ); + if (process.env.NODE_ENV === 'development') + router.use( + helmet.contentSecurityPolicy({ + directives: { + defaultSrc: ["'self'", "'unsafe-inline'", 'http://*'], + }, + }), + ); router.use(apolloMiddlware); From 4360d9749f291b918738e349df0eea0a78eea9e0 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 14 Aug 2020 14:50:01 +0200 Subject: [PATCH 33/34] docs(catalog/grqphql): updating docs --- plugins/catalog-graphql/README.md | 14 ++++++-------- plugins/graphql/README.md | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/plugins/catalog-graphql/README.md b/plugins/catalog-graphql/README.md index 3f85ec766e..cec3204c52 100644 --- a/plugins/catalog-graphql/README.md +++ b/plugins/catalog-graphql/README.md @@ -1,13 +1,11 @@ -# graphql +# Catalog GraphQL Plugin -Welcome to the graphql backend plugin! +## Getting Started -_This plugin was created through the Backstage CLI_ +This is the Catalog GraphQL plugin. -## Getting started +It provides the `catalog` part of the GraphQL schema. -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/graphql](http://localhost:3000/graphql). +To register it with the GraphQL backend, be sure to follow the [Gettting Started](../graphql/README.md#getting-started) guide of the GraphQL plugin. -You can also serve the plugin in isolation by running `yarn start` in the plugin directory. -This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. +