import * as E from "fp-ts/Either" import { BehaviorSubject, Subscription } from "rxjs" import { Subscription as WSubscription } from "wonka" import { pipe } from "fp-ts/function" import { GQLError, runGQLQuery, runGQLSubscription } from "../backend/GQLClient" import { GetTeamEnvironmentsDocument, TeamEnvironmentCreatedDocument, TeamEnvironmentDeletedDocument, TeamEnvironmentUpdatedDocument, } from "../backend/graphql" import { TeamEnvironment } from "./TeamEnvironment" type EntityType = "environment" type EntityID = `${EntityType}-${string}` export default class TeamEnvironmentAdapter { error$: BehaviorSubject | null> loading$: BehaviorSubject teamEnvironmentList$: BehaviorSubject /** * Stores the entity (environments) ids of all the loaded entities. * Used for preventing duplication of data which definitely is not possible (duplication due to network problems etc.) */ private entityIDs: Set private isDispose: boolean private teamEnvironmentCreated$: Subscription | null private teamEnvironmentUpdated$: Subscription | null private teamEnvironmentDeleted$: Subscription | null private teamEnvironmentCreatedSub: WSubscription | null private teamEnvironmentUpdatedSub: WSubscription | null private teamEnvironmentDeletedSub: WSubscription | null constructor(private teamID: string | undefined) { this.error$ = new BehaviorSubject | null>(null) this.loading$ = new BehaviorSubject(false) this.teamEnvironmentList$ = new BehaviorSubject([]) this.isDispose = true this.entityIDs = new Set() this.teamEnvironmentCreated$ = null this.teamEnvironmentDeleted$ = null this.teamEnvironmentUpdated$ = null this.teamEnvironmentCreatedSub = null this.teamEnvironmentDeletedSub = null this.teamEnvironmentUpdatedSub = null if (teamID) this.initialize() } unsubscribeSubscriptions() { this.teamEnvironmentCreated$?.unsubscribe() this.teamEnvironmentDeleted$?.unsubscribe() this.teamEnvironmentUpdated$?.unsubscribe() this.teamEnvironmentCreatedSub?.unsubscribe() this.teamEnvironmentDeletedSub?.unsubscribe() this.teamEnvironmentUpdatedSub?.unsubscribe() } changeTeamID(newTeamID: string | undefined) { this.teamID = newTeamID this.teamEnvironmentList$.next([]) this.loading$.next(false) this.entityIDs.clear() this.unsubscribeSubscriptions() if (this.teamID) this.initialize() } async initialize() { if (!this.isDispose) throw new Error(`Adapter is already initialized`) await this.fetchList() this.registerSubscriptions() } public dispose() { if (this.isDispose) throw new Error(`Adapter has been disposed`) this.isDispose = true this.unsubscribeSubscriptions() } async fetchList() { if (this.teamID === undefined) throw new Error("Team ID is null") this.loading$.next(true) const results: TeamEnvironment[] = [] const result = await runGQLQuery({ query: GetTeamEnvironmentsDocument, variables: { teamID: this.teamID, }, }) if (E.isLeft(result)) { this.error$.next(result.left) this.loading$.next(false) console.error(result.left) throw new Error(`Failed fetching team environments: ${result.left}`) } if (result.right.team !== undefined && result.right.team !== null) { results.push( ...result.right.team.teamEnvironments.map( (x) => { id: x.id, teamID: x.teamID, environment: { name: x.name, variables: JSON.parse(x.variables), }, } ) ) } // Add all the environments to the entity ids list results.forEach((env) => this.entityIDs.add(`environment-${env.id}`)) this.teamEnvironmentList$.next(results) this.loading$.next(false) } private createNewTeamEnvironment(newEnvironment: TeamEnvironment) { // Check if we have it already in the entity tree, if so, we don't need it again if (this.entityIDs.has(`environment-${newEnvironment.id}`)) return const teamEnvironments = this.teamEnvironmentList$.value teamEnvironments.push(newEnvironment) // Add to entity ids set this.entityIDs.add(`environment-${newEnvironment.id}`) this.teamEnvironmentList$.next(teamEnvironments) } private deleteTeamEnvironment(envId: string) { const teamEnvironments = this.teamEnvironmentList$.value.filter( ({ id }) => id !== envId ) this.entityIDs.delete(`environment-${envId}`) this.teamEnvironmentList$.next(teamEnvironments) } private updateTeamEnvironment(updatedEnvironment: TeamEnvironment) { const teamEnvironments = this.teamEnvironmentList$.value const environmentFound = teamEnvironments.find( ({ id }) => id === updatedEnvironment.id ) if (!environmentFound) return Object.assign(environmentFound, updatedEnvironment) this.teamEnvironmentList$.next(teamEnvironments) } private registerSubscriptions() { if (this.teamID === undefined) return const [teamEnvironmentCreated$, teamEnvironmentCreatedSub] = runGQLSubscription({ query: TeamEnvironmentCreatedDocument, variables: { teamID: this.teamID, }, }) this.teamEnvironmentCreatedSub = teamEnvironmentCreatedSub this.teamEnvironmentCreated$ = teamEnvironmentCreated$.subscribe( (result) => { if (E.isLeft(result)) { console.error(result.left) throw new Error(`Team Environment Create Error ${result.left}`) } this.createNewTeamEnvironment( pipe( result.right.teamEnvironmentCreated, (x) => { id: x.id, teamID: x.teamID, environment: { name: x.name, variables: JSON.parse(x.variables), }, } ) ) } ) const [teamEnvironmentDeleted$, teamEnvironmentDeletedSub] = runGQLSubscription({ query: TeamEnvironmentDeletedDocument, variables: { teamID: this.teamID, }, }) this.teamEnvironmentDeletedSub = teamEnvironmentDeletedSub this.teamEnvironmentDeleted$ = teamEnvironmentDeleted$.subscribe( (result) => { if (E.isLeft(result)) { console.error(result.left) throw new Error(`Team Environment Delete Error ${result.left}`) } this.deleteTeamEnvironment(result.right.teamEnvironmentDeleted.id) } ) const [teamEnvironmentUpdated$, teamEnvironmentUpdatedSub] = runGQLSubscription({ query: TeamEnvironmentUpdatedDocument, variables: { teamID: this.teamID, }, }) this.teamEnvironmentUpdatedSub = teamEnvironmentUpdatedSub this.teamEnvironmentUpdated$ = teamEnvironmentUpdated$.subscribe( (result) => { if (E.isLeft(result)) { console.error(result.left) throw new Error(`Team Environment Update Error ${result.left}`) } this.updateTeamEnvironment( pipe( result.right.teamEnvironmentUpdated, (x) => { id: x.id, teamID: x.teamID, environment: { name: x.name, variables: JSON.parse(x.variables), }, } ) ) } ) } }