From 982e053ce7f05d4ebfdb0384e2568b189cc797fa Mon Sep 17 00:00:00 2001 From: Max Ehrlicher-Schmidt Date: Fri, 13 Nov 2020 00:19:06 +0100 Subject: [PATCH] Add dynamic types in tables --- .../tableComponents/cell/cell.component.html | 6 +- .../tableComponents/cell/cell.component.ts | 19 ++- src/app/graphqlOperations/bikes.graphql | 2 +- .../fragments/introspection.graphql | 25 +++- .../isPartOfGraphQLFunction.ts | 2 +- .../pages/tables/bikes/bikes.component.html | 1 - src/app/pages/tables/bikes/bikes.component.ts | 39 ++--- src/app/services/bikes.service.ts | 5 +- src/app/services/schema.service.ts | 45 ++++++ src/generated/graphql.ts | 134 +++++++++++++++++- 10 files changed, 236 insertions(+), 42 deletions(-) create mode 100644 src/app/services/schema.service.ts diff --git a/src/app/components/tableComponents/cell/cell.component.html b/src/app/components/tableComponents/cell/cell.component.html index 349ce5f..6b65664 100644 --- a/src/app/components/tableComponents/cell/cell.component.html +++ b/src/app/components/tableComponents/cell/cell.component.html @@ -1,4 +1,4 @@ -
+
-
+
@@ -20,7 +20,7 @@
-
+
diff --git a/src/app/pages/tables/bikes/bikes.component.ts b/src/app/pages/tables/bikes/bikes.component.ts index 1e0f4d3..f37a34e 100644 --- a/src/app/pages/tables/bikes/bikes.component.ts +++ b/src/app/pages/tables/bikes/bikes.component.ts @@ -10,6 +10,7 @@ import { CargoBikeFieldsMutableFragmentDoc, CargoBikeUpdateInput, } from 'src/generated/graphql'; +import { SchemaService } from 'src/app/services/schema.service'; @Component({ selector: 'app-bikes', @@ -18,9 +19,9 @@ import { }) export class BikesComponent { columnInfo = [ - { name: 'name', header: 'Name', type: 'string', sticky: true }, - { name: 'id', header: 'ID', type: 'number', readonly: true }, - { name: 'group', header: 'Gruppe', type: 'enum', enumValues: [] }, + { name: 'name', header: 'Name', sticky: true }, + { name: 'id', header: 'ID', readonly: true }, + { name: 'group', header: 'Gruppe'}, ]; //properties that wont be shown in the table @@ -47,16 +48,13 @@ export class BikesComponent { relockingInterval = null; relockingDuration = 1000 * 60 * 1; - constructor(private bikesService: BikesService) { + constructor( + private bikesService: BikesService, + private schemaService: SchemaService + ) { this.displayedColumns.unshift(this.additionalColumnsFront[0]); this.displayedColumns.push(this.additionalColumnsBack[0]); - bikesService.groupEnum.subscribe((groupEnum) => { - this.columnInfo.find( - (column) => column.name === 'group' - ).enumValues = groupEnum; - }); - bikesService.loadingRowIds.subscribe((rowIds) => { this.loadingRowIds = rowIds; }); @@ -69,13 +67,13 @@ export class BikesComponent { if (this.data[0]) { this.displayedColumns = []; this.dataColumns = []; - + for (let index in this.data) { this.data[index] = flatten(this.data[index]); - } + } for (const prop in this.data[0]) { - if (!this.blacklistedColumns.includes(prop) && !prop.includes("__")) { + if (!this.blacklistedColumns.includes(prop) && !prop.includes('__')) { this.dataColumns.push(prop); } } @@ -122,17 +120,17 @@ export class BikesComponent { } getType(propertyName: string, row) { - //TODO: get type from introspection query + //console.log(propertyName, this.schemaService.getPropertyTypeFromSchema("CargoBike", propertyName)) return ( - this.columnInfo.find((column) => column.name === propertyName)?.type || - (typeof row[propertyName]) + this.schemaService.getPropertyTypeFromSchema("CargoBike", propertyName) ); } isReadonly(propertyName: string) { return ( this.columnInfo.find((column) => column.name === propertyName) - ?.readonly || !isPartOfGraphQLDoc(propertyName, CargoBikeFieldsMutableFragmentDoc) + ?.readonly || + !isPartOfGraphQLDoc(propertyName, CargoBikeFieldsMutableFragmentDoc) ); } @@ -143,13 +141,6 @@ export class BikesComponent { ); } - getEnumValues(propertyName: string) { - return ( - this.columnInfo.find((column) => column.name === propertyName) - ?.enumValues || [] - ); - } - isLoading(id: string) { return this.loadingRowIds.includes(id); } diff --git a/src/app/services/bikes.service.ts b/src/app/services/bikes.service.ts index f4e378b..6e69927 100644 --- a/src/app/services/bikes.service.ts +++ b/src/app/services/bikes.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; +import { SchemaService } from './schema.service'; import { GetCargoBikesGQL, GetCargoBikesQuery, @@ -28,6 +29,7 @@ export class BikesService { groupEnum: BehaviorSubject = new BehaviorSubject([]); constructor( + private schemaService: SchemaService, private getCargoBikesGQL: GetCargoBikesGQL, private getCargoBikeByIdGQL: GetCargoBikeByIdGQL, private updateCargoBikeGQL: UpdateCargoBikeGQL, @@ -50,8 +52,7 @@ export class BikesService { loadBikes() { this.getCargoBikesGQL.fetch().subscribe((result) => { this.bikes.next(result.data.cargoBikes); - let enumValues = result.data.__type.enumValues.map((value) => value.name); - this.groupEnum.next(enumValues); + this.schemaService.nextSchema(result.data.__schema); }); } diff --git a/src/app/services/schema.service.ts b/src/app/services/schema.service.ts new file mode 100644 index 0000000..952fd40 --- /dev/null +++ b/src/app/services/schema.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { find } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class SchemaService { + schema: BehaviorSubject = new BehaviorSubject({}); + + nextSchema(schema: any) { + this.schema.next(schema); + } + + /** expects startingObject and variablePath and returns its type e.g. cargoBike, security.name -> returns the type of the name variable */ + getPropertyTypeFromSchema( + startingObjectName: String, + variable: String + ): String { + const variablePath = variable.split('.'); + const types = this.schema.value.types; + const startingObject = types.find( + (type) => type.name === startingObjectName + ); + const field = startingObject.fields.find( + (field) => field.name === variablePath[0] + ); + const type = field.type.name || field.type.ofType.name; + if (variablePath.length === 1) { + if ((field.type.kind === "ENUM")) { + return "Enum//" + this.getEnumValuesFromSchema(field.type.name).join("//"); + } + return type; + } else { + return this.getPropertyTypeFromSchema(type, variablePath.slice(1).join('.')); + } + } + + + getEnumValuesFromSchema(typeName: String): String[] { + const types= this.schema.value.types; + const type = types.find(type => type.name === typeName); + return type.enumValues.map((value) => value.name); + } +} diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index 2db4516..fe00cb6 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -1668,6 +1668,22 @@ export enum CacheControlScope { } +/** A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations. */ +export type __Schema = { + __typename?: '__Schema'; + description?: Maybe; + /** A list of all types supported by this server. */ + types: Array<__Type>; + /** The type that query operations will be rooted at. */ + queryType: __Type; + /** If this server supports mutation, the type that mutation operations will be rooted at. */ + mutationType?: Maybe<__Type>; + /** If this server support subscription, the type that subscription operations will be rooted at. */ + subscriptionType?: Maybe<__Type>; + /** A list of all directives supported by this server. */ + directives: Array<__Directive>; +}; + /** * The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. * @@ -1757,6 +1773,62 @@ export type __EnumValue = { deprecationReason?: Maybe; }; +/** + * A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. + * + * In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor. + */ +export type __Directive = { + __typename?: '__Directive'; + name: Scalars['String']; + description?: Maybe; + isRepeatable: Scalars['Boolean']; + locations: Array<__DirectiveLocation>; + args: Array<__InputValue>; +}; + +/** A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies. */ +export enum __DirectiveLocation { + /** Location adjacent to a query operation. */ + Query = 'QUERY', + /** Location adjacent to a mutation operation. */ + Mutation = 'MUTATION', + /** Location adjacent to a subscription operation. */ + Subscription = 'SUBSCRIPTION', + /** Location adjacent to a field. */ + Field = 'FIELD', + /** Location adjacent to a fragment definition. */ + FragmentDefinition = 'FRAGMENT_DEFINITION', + /** Location adjacent to a fragment spread. */ + FragmentSpread = 'FRAGMENT_SPREAD', + /** Location adjacent to an inline fragment. */ + InlineFragment = 'INLINE_FRAGMENT', + /** Location adjacent to a variable definition. */ + VariableDefinition = 'VARIABLE_DEFINITION', + /** Location adjacent to a schema definition. */ + Schema = 'SCHEMA', + /** Location adjacent to a scalar definition. */ + Scalar = 'SCALAR', + /** Location adjacent to an object type definition. */ + Object = 'OBJECT', + /** Location adjacent to a field definition. */ + FieldDefinition = 'FIELD_DEFINITION', + /** Location adjacent to an argument definition. */ + ArgumentDefinition = 'ARGUMENT_DEFINITION', + /** Location adjacent to an interface definition. */ + Interface = 'INTERFACE', + /** Location adjacent to a union definition. */ + Union = 'UNION', + /** Location adjacent to an enum definition. */ + Enum = 'ENUM', + /** Location adjacent to an enum value definition. */ + EnumValue = 'ENUM_VALUE', + /** Location adjacent to an input object type definition. */ + InputObject = 'INPUT_OBJECT', + /** Location adjacent to an input object field definition. */ + InputFieldDefinition = 'INPUT_FIELD_DEFINITION' +} + export type GetCargoBikeByIdQueryVariables = Exact<{ id: Scalars['ID']; }>; @@ -1818,7 +1890,7 @@ export type GetCargoBikesQuery = ( { __typename?: 'CargoBike' } & CargoBikeFieldsFragment )>> } - & IntrospectionFragment + & SchemaIntrospectionFragment ); export type BikeEventFieldsFragment = ( @@ -1867,7 +1939,7 @@ export type CargoBikeFieldsFragment = ( & CargoBikeFieldsMutableFragment ); -export type IntrospectionFragment = ( +export type GroupIntrospectionFragment = ( { __typename?: 'Query' } & { __type?: Maybe<( { __typename?: '__Type' } @@ -1879,6 +1951,32 @@ export type IntrospectionFragment = ( )> } ); +export type SchemaIntrospectionFragment = ( + { __typename?: 'Query' } + & { __schema: ( + { __typename?: '__Schema' } + & { types: Array<( + { __typename?: '__Type' } + & Pick<__Type, 'name' | 'kind'> + & { enumValues?: Maybe + )>>, fields?: Maybe + & { type: ( + { __typename?: '__Type' } + & Pick<__Type, 'name' | 'kind'> + & { ofType?: Maybe<( + { __typename?: '__Type' } + & Pick<__Type, 'name' | 'kind'> + )> } + ) } + )>> } + )> } + ) } +); + export type LendingStationFieldsGeneralFragment = ( { __typename?: 'LendingStation' } & Pick @@ -2050,8 +2148,8 @@ export const CargoBikeFieldsFragmentDoc = gql` ${CargoBikeFieldsMutableFragmentDoc} ${ProviderFieldsGeneralFragmentDoc} ${LendingStationFieldsGeneralFragmentDoc}`; -export const IntrospectionFragmentDoc = gql` - fragment Introspection on Query { +export const GroupIntrospectionFragmentDoc = gql` + fragment GroupIntrospection on Query { __type(name: "Group") { name enumValues { @@ -2060,6 +2158,30 @@ export const IntrospectionFragmentDoc = gql` } } `; +export const SchemaIntrospectionFragmentDoc = gql` + fragment SchemaIntrospection on Query { + __schema { + types { + name + kind + enumValues { + name + } + fields { + name + type { + name + kind + ofType { + name + kind + } + } + } + } + } +} + `; export const GetCargoBikeByIdDocument = gql` query GetCargoBikeById($id: ID!) { cargoBikeById(id: $id) { @@ -2134,12 +2256,12 @@ export const UnlockCargoBikeDocument = gql` } export const GetCargoBikesDocument = gql` query GetCargoBikes { - ...Introspection + ...SchemaIntrospection cargoBikes(limit: 100, offset: 0) { ...CargoBikeFields } } - ${IntrospectionFragmentDoc} + ${SchemaIntrospectionFragmentDoc} ${CargoBikeFieldsFragmentDoc}`; @Injectable({