From b9c21c6fbaeedd5ea3de4295736b8c5e18d9e961 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Aug 2020 07:27:32 +0200 Subject: [PATCH] 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); + } +}