chore(gql): tidying up the graphql code

This commit is contained in:
blam
2020-08-05 07:27:32 +02:00
parent de1ddc1096
commit b9c21c6fba
7 changed files with 68 additions and 328 deletions
+1
View File
@@ -0,0 +1 @@
src/graphql/types.ts
+6 -5
View File
@@ -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}>
+3
View File
@@ -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",
@@ -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> = T | null;
export type Exact<T extends { [key: string]: unknown }> = {
[K in keyof T]: T[K];
};
export type RequireFields<T, K extends keyof T> = {
[X in Exclude<keyof T, K>]?: T[X];
} &
{ [P in K]-?: NonNullable<T[P]> };
/** 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<Scalars['String']>;
annotation?: Maybe<EntityMetadataAnnotation>;
annotations: Array<EntityMetadataAnnotation>;
};
export type EntityMetadataAnnotationArgs = {
name?: Maybe<Scalars['String']>;
};
export type CatalogEntity = {
__typename?: 'CatalogEntity';
apiVersion?: Maybe<Scalars['String']>;
kind: Scalars['String'];
metadata?: Maybe<EntityMetadata>;
};
export type CatalogQuery = {
__typename?: 'CatalogQuery';
list: Array<CatalogEntity>;
};
export type Query = {
__typename?: 'Query';
catalog: CatalogQuery;
};
export type ResolverTypeWrapper<T> = Promise<T> | T;
export type LegacyStitchingResolver<TResult, TParent, TContext, TArgs> = {
fragment: string;
resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
};
export type NewStitchingResolver<TResult, TParent, TContext, TArgs> = {
selectionSet: string;
resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
};
export type StitchingResolver<TResult, TParent, TContext, TArgs> =
| LegacyStitchingResolver<TResult, TParent, TContext, TArgs>
| NewStitchingResolver<TResult, TParent, TContext, TArgs>;
export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> =
| ResolverFn<TResult, TParent, TContext, TArgs>
| StitchingResolver<TResult, TParent, TContext, TArgs>;
export type ResolverFn<TResult, TParent, TContext, TArgs> = (
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo,
) => Promise<TResult> | TResult;
export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo,
) => AsyncIterator<TResult> | Promise<AsyncIterator<TResult>>;
export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo,
) => TResult | Promise<TResult>;
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<TResult, TParent, TContext, TArgs> {
subscribe: SubscriptionSubscribeFn<any, TParent, TContext, TArgs>;
resolve: SubscriptionResolveFn<TResult, any, TContext, TArgs>;
}
export type SubscriptionObject<
TResult,
TKey extends string,
TParent,
TContext,
TArgs
> =
| SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs>
| SubscriptionResolverObject<TResult, TParent, TContext, TArgs>;
export type SubscriptionResolver<
TResult,
TKey extends string,
TParent = {},
TContext = {},
TArgs = {}
> =
| ((
...args: any[]
) => SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>)
| SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>;
export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = (
parent: TParent,
context: TContext,
info: GraphQLResolveInfo,
) => Maybe<TTypes> | Promise<Maybe<TTypes>>;
export type IsTypeOfResolverFn<T = {}> = (
obj: T,
info: GraphQLResolveInfo,
) => boolean | Promise<boolean>;
export type NextResolverFn<T> = () => Promise<T>;
export type DirectiveResolverFn<
TResult = {},
TParent = {},
TContext = {},
TArgs = {}
> = (
next: NextResolverFn<TResult>,
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo,
) => TResult | Promise<TResult>;
/** Mapping between all available schema types and the resolvers types */
export type ResolversTypes = {
EntityMetadataAnnotation: ResolverTypeWrapper<EntityMetadataAnnotation>;
String: ResolverTypeWrapper<Scalars['String']>;
EntityMetadata: ResolverTypeWrapper<EntityMetadata>;
CatalogEntity: ResolverTypeWrapper<CatalogEntity>;
CatalogQuery: ResolverTypeWrapper<CatalogQuery>;
Query: ResolverTypeWrapper<{}>;
Boolean: ResolverTypeWrapper<Scalars['Boolean']>;
};
/** 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<ResolversTypes['String'], ParentType, ContextType>;
value?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType>;
};
export type EntityMetadataResolvers<
ContextType = ModuleContext,
ParentType extends ResolversParentTypes['EntityMetadata'] = ResolversParentTypes['EntityMetadata']
> = {
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
namespace?: Resolver<
Maybe<ResolversTypes['String']>,
ParentType,
ContextType
>;
annotation?: Resolver<
Maybe<ResolversTypes['EntityMetadataAnnotation']>,
ParentType,
ContextType,
RequireFields<EntityMetadataAnnotationArgs, never>
>;
annotations?: Resolver<
Array<ResolversTypes['EntityMetadataAnnotation']>,
ParentType,
ContextType
>;
__isTypeOf?: IsTypeOfResolverFn<ParentType>;
};
export type CatalogEntityResolvers<
ContextType = ModuleContext,
ParentType extends ResolversParentTypes['CatalogEntity'] = ResolversParentTypes['CatalogEntity']
> = {
apiVersion?: Resolver<
Maybe<ResolversTypes['String']>,
ParentType,
ContextType
>;
kind?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
metadata?: Resolver<
Maybe<ResolversTypes['EntityMetadata']>,
ParentType,
ContextType
>;
__isTypeOf?: IsTypeOfResolverFn<ParentType>;
};
export type CatalogQueryResolvers<
ContextType = ModuleContext,
ParentType extends ResolversParentTypes['CatalogQuery'] = ResolversParentTypes['CatalogQuery']
> = {
list?: Resolver<
Array<ResolversTypes['CatalogEntity']>,
ParentType,
ContextType
>;
__isTypeOf?: IsTypeOfResolverFn<ParentType>;
};
export type QueryResolvers<
ContextType = ModuleContext,
ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']
> = {
catalog?: Resolver<ResolversTypes['CatalogQuery'], ParentType, ContextType>;
};
export type Resolvers<ContextType = ModuleContext> = {
EntityMetadataAnnotation?: EntityMetadataAnnotationResolvers<ContextType>;
EntityMetadata?: EntityMetadataResolvers<ContextType>;
CatalogEntity?: CatalogEntityResolvers<ContextType>;
CatalogQuery?: CatalogQueryResolvers<ContextType>;
Query?: QueryResolvers<ContextType>;
};
/**
* @deprecated
* Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config.
*/
export type IResolvers<ContextType = ModuleContext> = Resolvers<ContextType>;
+27 -34
View File
@@ -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({
+1 -1
View File
@@ -7,7 +7,7 @@ type EntityMetadata {
name: String!
namespace: String
annotation(name: String): EntityMetadataAnnotation
annotations: [EntityMetadataAnnotation!]!
annotations: [EntityMetadataAnnotation!]
}
type CatalogEntity {
@@ -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<Entity[]> {
const res = await fetch(`${this.baseUrl}/catalog/entities`);
if (!res.ok) {
throw new Error(`NOPE, ${await res.text()}`);
}
return await (res.json() as Promise<Entity[]>);
}
}