From 572a95d4c0a0c5ec686151a79bac10b35ef524ca Mon Sep 17 00:00:00 2001 From: Darri Steinn Konradsson Date: Mon, 10 Jan 2022 11:05:21 +0000 Subject: [PATCH 1/5] Where and before should be optional --- libs/nest/pagination/src/lib/paginate.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/nest/pagination/src/lib/paginate.ts b/libs/nest/pagination/src/lib/paginate.ts index eb181cb1501e..34fc6b1c0431 100644 --- a/libs/nest/pagination/src/lib/paginate.ts +++ b/libs/nest/pagination/src/lib/paginate.ts @@ -116,9 +116,9 @@ export interface PaginateInput { Model: any primaryKeyField: string orderOption: any - where: any + where?: any after: string - before: string + before?: string limit: number [key: string]: any } @@ -130,18 +130,18 @@ export interface PageInfo { endCursor: string } -export async function paginate({ +export async function paginate({ Model, primaryKeyField, orderOption, - where, + where = {}, after, before, limit, ...queryArgs }: PaginateInput): Promise<{ totalCount: number - data: any[] + data: T[] pageInfo: PageInfo }> { let order = normalizeOrder(orderOption, primaryKeyField) From 889c3703b1b42d8551fe17fcaf95e20fd27fa510 Mon Sep 17 00:00:00 2001 From: Darri Steinn Konradsson Date: Mon, 10 Jan 2022 11:06:20 +0000 Subject: [PATCH 2/5] Relay like pagination of skilavottordAllDeregisteredVehicles --- .../RecyclingFund/Overview/Overview.tsx | 32 +++++++---- .../src/app/modules/vehicle/vehicle.model.ts | 27 +++++++++ .../app/modules/vehicle/vehicle.resolver.ts | 35 ++++++------ .../app/modules/vehicle/vehicle.service.ts | 57 ++++++++----------- 4 files changed, 88 insertions(+), 63 deletions(-) diff --git a/apps/skilavottord/web/screens/RecyclingFund/Overview/Overview.tsx b/apps/skilavottord/web/screens/RecyclingFund/Overview/Overview.tsx index 98a27a7c9e49..bb12892952aa 100644 --- a/apps/skilavottord/web/screens/RecyclingFund/Overview/Overview.tsx +++ b/apps/skilavottord/web/screens/RecyclingFund/Overview/Overview.tsx @@ -21,16 +21,23 @@ import { Query, Role } from '@island.is/skilavottord-web/graphql/schema' import { CarsTable, RecyclingCompanyImage } from './components' export const SkilavottordVehiclesQuery = gql` - query skilavottordVehiclesQuery { - skilavottordAllDeregisteredVehicles { - vehicleId - vehicleType - newregDate - createdAt - recyclingRequests { - id - nameOfRequestor + query skilavottordVehiclesQuery($after: String!) { + skilavottordAllDeregisteredVehicles(first: 10, after: $after) { + pageInfo { + endCursor + hasNextPage + } + count + items { + vehicleId + vehicleType + newregDate createdAt + recyclingRequests { + id + nameOfRequestor + createdAt + } } } } @@ -42,8 +49,11 @@ const Overview: FC = () => { t: { recyclingFundOverview: t, recyclingFundSidenav: sidenavText, routes }, } = useI18n() - const { data } = useQuery(SkilavottordVehiclesQuery) - const vehicles = data?.skilavottordAllDeregisteredVehicles ?? [] + const { data } = useQuery(SkilavottordVehiclesQuery, { + variables: { after: '' }, + }) + const { pageInfo, count, items: vehicles } = + data?.skilavottordAllDeregisteredVehicles ?? {} if (!user) { return null diff --git a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts index 42a356843eeb..1a2b3a315c1b 100644 --- a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts +++ b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts @@ -74,3 +74,30 @@ export class VehicleModel extends Model { @HasMany(() => RecyclingRequestModel) recyclingRequests!: RecyclingRequestModel[] } + +@ObjectType() +export class PageInfo { + @Field() + hasNextPage!: boolean + + @Field() + hasPreviousPage!: boolean + + @Field({ nullable: true }) + startCursor!: string + + @Field({ nullable: true }) + endCursor!: string +} + +@ObjectType() +export class VehicleConnection { + @Field(() => PageInfo) + pageInfo!: PageInfo + + @Field() + count!: number + + @Field(() => [VehicleModel]) + items: VehicleModel[] +} diff --git a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.resolver.ts b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.resolver.ts index 2217a7af6038..f64b7a3dfbf7 100644 --- a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.resolver.ts +++ b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.resolver.ts @@ -1,5 +1,5 @@ import { Inject, NotFoundException, forwardRef } from '@nestjs/common' -import { Query, Resolver, Mutation, Args } from '@nestjs/graphql' +import { Query, Resolver, Mutation, Args, Int } from '@nestjs/graphql' import parse from 'date-fns/parse' import type { Logger } from '@island.is/logging' @@ -7,7 +7,7 @@ import { LOGGER_PROVIDER } from '@island.is/logging' import { Authorize, CurrentUser, User, Role } from '../auth' -import { VehicleModel } from './vehicle.model' +import { VehicleModel, VehicleConnection } from './vehicle.model' import { VehicleService } from './vehicle.service' import { SamgongustofaService } from '../samgongustofa' @@ -23,23 +23,22 @@ export class VehicleResolver { ) {} @Authorize({ roles: [Role.developer, Role.recyclingCompany] }) - @Query(() => [VehicleModel]) - async skilavottordAllVehicles(): Promise { - const vehicles = await this.vehicleService.findAll() - this.logger.info( - 'getAllVehicle response:' + JSON.stringify(vehicles, null, 2), - ) - return vehicles - } - @Authorize({ roles: [Role.developer, Role.recyclingFund] }) - @Query(() => [VehicleModel]) - async skilavottordAllDeregisteredVehicles(): Promise { - const deregisteredVehicles = await this.vehicleService.findAllDeregistered() - this.logger.info( - 'getAllVehicle response:' + JSON.stringify(deregisteredVehicles, null, 2), - ) - return deregisteredVehicles + @Query(() => VehicleConnection) + async skilavottordAllDeregisteredVehicles( + @Args('first', { type: () => Int }) first: number, + @Args('after') after: string, + ): Promise { + const { + pageInfo, + totalCount, + data, + } = await this.vehicleService.findAllDeregistered(first, after) + return { + pageInfo, + count: totalCount, + items: data, + } } @Query(() => VehicleModel) diff --git a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.service.ts b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.service.ts index 5a432d372b3c..37e8dfd595f6 100644 --- a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.service.ts +++ b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.service.ts @@ -1,7 +1,9 @@ import { Inject, Injectable } from '@nestjs/common' +import { InjectModel } from '@nestjs/sequelize' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' +import { paginate } from '@island.is/nest/pagination' import { RecyclingRequestModel } from '../recyclingRequest' import { RecyclingPartnerModel } from '../recyclingPartner' @@ -12,44 +14,31 @@ export class VehicleService { constructor( @Inject(LOGGER_PROVIDER) private logger: Logger, + @InjectModel(VehicleModel) + private vehicleModel: VehicleModel, ) {} - async findAll(): Promise { - this.logger.info('Getting all vehicles...') - try { - return await VehicleModel.findAll({ - include: [ - { - model: RecyclingRequestModel, + async findAllDeregistered(first: number, after: string) { + return paginate({ + Model: this.vehicleModel, + limit: first, + after, + primaryKeyField: 'vehicleId', + orderOption: [['updatedAt', 'DESC']], + include: [ + { + model: RecyclingRequestModel, + where: { + requestType: 'deregistered', }, - ], - }) - } catch (error) { - this.logger.error('error finding all vehicles:' + error) - } - } - - async findAllDeregistered(): Promise { - this.logger.info('finding all deregistered vehicles...') - try { - return await VehicleModel.findAll({ - include: [ - { - model: RecyclingRequestModel, - where: { - requestType: 'deregistered', + include: [ + { + model: RecyclingPartnerModel, }, - include: [ - { - model: RecyclingPartnerModel, - }, - ], - }, - ], - }) - } catch (error) { - this.logger.error('error finding all deregistered vehicles:' + error) - } + ], + }, + ], + }) } async findByVehicleId(vehicleId: string): Promise { From 16969956cc4dd6e16b263bafc3e7a3661fcb1cf0 Mon Sep 17 00:00:00 2001 From: Darri Steinn Konradsson Date: Mon, 10 Jan 2022 11:07:07 +0000 Subject: [PATCH 3/5] Cleanup schema --- .../recyclingRequest.model.ts | 8 ++++---- .../src/app/modules/vehicle/vehicle.model.ts | 11 +++++----- .../vehicleOwner/vehicleOwner.resolver.ts | 20 ------------------- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.model.ts b/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.model.ts index 64892be94db3..b4ac04160cbe 100644 --- a/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.model.ts +++ b/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.model.ts @@ -108,17 +108,17 @@ export class RecyclingRequestModel extends Model { }) nameOfRequestor: string - @Field() + @Field({ nullable: true }) @CreatedAt @Column({ field: 'created_at', }) - createdAt: Date + createdAt?: Date - @Field() + @Field({ nullable: true }) @UpdatedAt @Column({ field: 'updated_at', }) - updatedAt: Date + updatedAt?: Date } diff --git a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts index 1a2b3a315c1b..6e436fc057d1 100644 --- a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts +++ b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts @@ -48,11 +48,11 @@ export class VehicleModel extends Model { }) vehicleColor: string - @Field() + @Field({ nullable: true }) @Column({ type: DataType.DATE, }) - newregDate: Date + newregDate?: Date @Field() @Column({ @@ -61,14 +61,15 @@ export class VehicleModel extends Model { vinNumber: string @Field() + @Field({ nullable: true }) @CreatedAt @Column - createdAt: Date + createdAt?: Date - @Field() + @Field({ nullable: true }) @UpdatedAt @Column - updatedAt: Date + updatedAt?: Date @Field(() => [RecyclingRequestModel], { nullable: true }) @HasMany(() => RecyclingRequestModel) diff --git a/apps/skilavottord/ws/src/app/modules/vehicleOwner/vehicleOwner.resolver.ts b/apps/skilavottord/ws/src/app/modules/vehicleOwner/vehicleOwner.resolver.ts index 46b80a8a3809..7c0f5faca3db 100644 --- a/apps/skilavottord/ws/src/app/modules/vehicleOwner/vehicleOwner.resolver.ts +++ b/apps/skilavottord/ws/src/app/modules/vehicleOwner/vehicleOwner.resolver.ts @@ -18,26 +18,6 @@ export class VehicleOwnerResolver { private logger: Logger, ) {} - @Query(() => [VehicleOwnerModel]) - async skilavottordAllVehicleOwners(): Promise { - const res = await this.vehicleOwnerService.findAll() - this.logger.debug( - 'getAllVehicleOwners responce:' + JSON.stringify(res, null, 2), - ) - return res - } - - @Query(() => VehicleOwnerModel) - async skilavottordVehiclesFromLocal( - @CurrentUser() user: User, - ): Promise { - const res = await this.vehicleOwnerService.findByNationalId(user.nationalId) - this.logger.warn( - 'getVehicleOwnersByNationaId responce:' + JSON.stringify(res, null, 2), - ) - return res - } - @Authorize({ roles: [Role.developer, Role.recyclingCompany, Role.recyclingFund], }) From 2a9dee302bb8e79b8eca407f905d1b09857277cd Mon Sep 17 00:00:00 2001 From: Maciej Date: Mon, 10 Jan 2022 15:26:57 +0000 Subject: [PATCH 4/5] add infinite scroll --- .../RecyclingFund/Overview/Overview.tsx | 74 ++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/apps/skilavottord/web/screens/RecyclingFund/Overview/Overview.tsx b/apps/skilavottord/web/screens/RecyclingFund/Overview/Overview.tsx index bb12892952aa..c09a45faa082 100644 --- a/apps/skilavottord/web/screens/RecyclingFund/Overview/Overview.tsx +++ b/apps/skilavottord/web/screens/RecyclingFund/Overview/Overview.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react' +import React, { FC, useContext, useRef, useEffect } from 'react' import gql from 'graphql-tag' import { useQuery } from '@apollo/client' import NextLink from 'next/link' @@ -9,6 +9,7 @@ import { Breadcrumbs, GridColumn, GridRow, + LoadingDots, Stack, Text, } from '@island.is/island-ui/core' @@ -22,7 +23,7 @@ import { CarsTable, RecyclingCompanyImage } from './components' export const SkilavottordVehiclesQuery = gql` query skilavottordVehiclesQuery($after: String!) { - skilavottordAllDeregisteredVehicles(first: 10, after: $after) { + skilavottordAllDeregisteredVehicles(first: 20, after: $after) { pageInfo { endCursor hasNextPage @@ -48,13 +49,55 @@ const Overview: FC = () => { const { t: { recyclingFundOverview: t, recyclingFundSidenav: sidenavText, routes }, } = useI18n() - - const { data } = useQuery(SkilavottordVehiclesQuery, { - variables: { after: '' }, - }) - const { pageInfo, count, items: vehicles } = + const { data, loading, fetchMore } = useQuery( + SkilavottordVehiclesQuery, + { + notifyOnNetworkStatusChange: true, + variables: { after: '' }, + }, + ) + const { pageInfo, items: vehicles } = data?.skilavottordAllDeregisteredVehicles ?? {} + const triggerRef = useRef(null) + + useEffect(() => { + if (loading) return + const observer = new IntersectionObserver( + (entries) => { + const { hasNextPage, endCursor } = pageInfo + if (entries[0].isIntersecting && hasNextPage) { + fetchMore({ + variables: { after: endCursor }, + updateQuery: ( + { skilavottordAllDeregisteredVehicles }, + { fetchMoreResult }, + ) => { + const prevResults = skilavottordAllDeregisteredVehicles + const newResults = + fetchMoreResult?.skilavottordAllDeregisteredVehicles + return { + skilavottordAllDeregisteredVehicles: { + ...newResults, + items: [...prevResults?.items, ...newResults?.items], + }, + } + }, + }) + } + }, + { + root: null, + rootMargin: '0px', + threshold: 1.0, + }, + ) + triggerRef.current && observer.observe(triggerRef.current) + return () => { + observer.disconnect() + } + }, [loading]) + if (!user) { return null } else if (!hasPermission('recycledVehicles', user?.role as Role)) { @@ -132,11 +175,24 @@ const Overview: FC = () => { {t.subtitles.deregistered} - {vehicles.length > 0 ? ( - + {vehicles?.length > 0 ? ( + <> + + + {loading && ( + + + + )} + + ) : loading ? ( + + + ) : ( {t.info} )} +
From 12c64808bbeb665bbdca1cb329823ab725eefd4f Mon Sep 17 00:00:00 2001 From: Darri Steinn Konradsson Date: Mon, 10 Jan 2022 16:13:05 +0000 Subject: [PATCH 5/5] Provide default type for generic --- libs/nest/pagination/src/lib/paginate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/nest/pagination/src/lib/paginate.ts b/libs/nest/pagination/src/lib/paginate.ts index 34fc6b1c0431..368deefebcba 100644 --- a/libs/nest/pagination/src/lib/paginate.ts +++ b/libs/nest/pagination/src/lib/paginate.ts @@ -130,7 +130,7 @@ export interface PageInfo { endCursor: string } -export async function paginate({ +export async function paginate({ Model, primaryKeyField, orderOption,