Skip to content

Commit

Permalink
feat: add aggregations store and update methods
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismclarke committed Mar 6, 2022
1 parent 16b42be commit 3f2c737
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 5 deletions.
94 changes: 94 additions & 0 deletions src/stores/Aggregations/aggregations.store.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { action, makeAutoObservable, observable } from 'mobx'
import { createContext, useContext } from 'react'
import { Subscription } from 'rxjs'
import { RootStore } from '..'
import { DatabaseV2 } from '../databaseV2'

// TODO - refactor to shared list (search targetDocId)
const targetDocIds = ['users_votedUsefulHowtos', 'users_verified'] as const

type IAggregationId = typeof targetDocIds[number]

const DEFAULT_TIMEOUT = 1000 * 60 * 5

export class AggregationsStore {
@observable aggregations: {
[aggregationId in IAggregationId]: any
}

private db: DatabaseV2
private subscriptions$: { [aggregationId: string]: Subscription } = {}
private timeouts$: {
[aggregationId: string]: NodeJS.Timeout
} = {}

constructor(rootStore: RootStore) {
this.db = rootStore.dbV2
// By default initialisise all aggregations as empty documents
const aggregations: any = {}
for (const targetDocId of targetDocIds) {
aggregations[targetDocId] = {}
}
this.aggregations = aggregations
makeAutoObservable(this)
}

@action
private updateAggregationValue(aggregationId: IAggregationId, value: any) {
this.aggregations[aggregationId] = value
}

/**
* Subscribe to updates to a particularly aggregation. Includes a default timeout so that subscriptions are not
* left hanging too much longer than required, but at the same time are not repeatedly created to re-fetch same data
*/
updateAggregation(
aggregationId: IAggregationId,
timeoutDuration = DEFAULT_TIMEOUT,
) {
if (!this.subscriptions$.hasOwnProperty(aggregationId)) {
const subscription = this.db
.collection('aggregations')
.doc(aggregationId)
.stream()
.subscribe(value => this.updateAggregationValue(aggregationId, value))
this.subscriptions$[aggregationId] = subscription
}
this.setSubscriptionTimeout(aggregationId, timeoutDuration)
}

/**
* Use window timeout methods to handle termination of subscriptions. Setting a new timeout on an
* existing subscription will simply extend the time it is maintained for
*/
private setSubscriptionTimeout(
aggregationId: IAggregationId,
timeoutDuration: number,
) {
// remove any existing timeout
if (this.timeouts$.hasOwnProperty(aggregationId)) {
clearTimeout(this.timeouts$[aggregationId])
}
// add timeout to unsubscribe
this.timeouts$[aggregationId] = setTimeout(() => {
return this.clearSubscription(aggregationId)
}, timeoutDuration)
}

private clearSubscription(aggregationId: IAggregationId) {
if (this.subscriptions$.hasOwnProperty(aggregationId)) {
this.subscriptions$[aggregationId].unsubscribe()
delete this.subscriptions$[aggregationId]
}
}
}

/**
* Export an empty context object to be shared with components
* The context will be populated with the researchStore in the module index
* (avoids cyclic deps and ensure shared module ready)
*/
export const AggregationsStoreContext = createContext<AggregationsStore>(
null as any,
)
export const useAggregationsStore = () => useContext(AggregationsStoreContext)
3 changes: 3 additions & 0 deletions src/stores/common/module.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export class ModuleStore {
get mapsStore() {
return this.rootStore.stores.mapsStore
}
get aggregationsStore() {
return this.rootStore.stores.aggregationsStore
}

/****************************************************************************
* Database Management Methods
Expand Down
11 changes: 6 additions & 5 deletions src/stores/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ import { DatabaseV2 } from './databaseV2'
import { MobileMenuStore } from './MobileMenu/mobilemenu.store'
import { AdminStore } from './Admin/admin.store'
import { ThemeStore } from './Theme/theme.store'
import { AggregationsStore } from './Aggregations/aggregations.store'

export class RootStore {
dbV2 = new DatabaseV2()
stores: IStores
constructor() {
this.stores = stores(this)
}
stores = stores(this)
}

// the following stores are passed into a top level app provider and can be accessed through @inject
Expand All @@ -26,7 +24,8 @@ export class RootStore {
// as these will be called immediately, and instead use init() or similar methods that can be called
// from a page (see common/module store for example)
const stores = (rootStore: RootStore) => {
return {
const stores: IStores = {
aggregationsStore: new AggregationsStore(rootStore),
howtoStore: new HowtoStore(rootStore),
userStore: new UserStore(rootStore),
templateStore: new TemplateStore(rootStore),
Expand All @@ -38,6 +37,7 @@ const stores = (rootStore: RootStore) => {
adminStore: new AdminStore(rootStore),
themeStore: new ThemeStore(),
}
return stores
}

export interface IStores {
Expand All @@ -51,4 +51,5 @@ export interface IStores {
mapsStore: MapsStore
adminStore: AdminStore
themeStore: ThemeStore
aggregationsStore: AggregationsStore
}

0 comments on commit 3f2c737

Please sign in to comment.