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.