-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Foundations for Warnings System #161
Changes from 9 commits
73f4cc2
63f5267
2e2f189
9ad76a0
3dc09b5
c591f6b
926a7bf
2a5a813
cb43cc4
6a76226
975489e
64e5c32
fc7d22d
cee8df2
bffc12d
30654b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import _ from 'lodash'; | ||
import { AxiosInstance } from 'axios'; | ||
import ChtSession from './cht-session'; | ||
import { Config, ContactType } from '../config'; | ||
import { UserPayload } from '../services/user-payload'; | ||
import { AxiosInstance } from 'axios'; | ||
|
||
export type PlacePayload = { | ||
name: string; | ||
|
@@ -18,17 +18,6 @@ export type PlacePayload = { | |
[key: string]: any; | ||
}; | ||
|
||
export type RemotePlace = { | ||
id: string; | ||
name: string; | ||
lineage: string[]; | ||
ambiguities?: RemotePlace[]; | ||
|
||
// sadly, sometimes invalid or uncreated objects "pretend" to be remote | ||
// should reconsider this naming | ||
type: 'remote' | 'local' | 'invalid'; | ||
}; | ||
|
||
export class ChtApi { | ||
public readonly chtSession: ChtSession; | ||
private axiosInstance: AxiosInstance; | ||
|
@@ -174,26 +163,15 @@ export class ChtApi { | |
}; | ||
|
||
getPlacesWithType = async (placeType: string) | ||
: Promise<RemotePlace[]> => { | ||
const url = `medic/_design/medic-client/_view/contacts_by_type_freetext`; | ||
: Promise<any[]> => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this array of type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not yet. Right now it returns the raw documents from CouchDB. I use |
||
const url = `medic/_design/medic-client/_view/contacts_by_type`; | ||
const params = { | ||
startkey: JSON.stringify([ placeType, 'name:']), | ||
endkey: JSON.stringify([ placeType, 'name:\ufff0']), | ||
key: JSON.stringify([placeType]), | ||
include_docs: true, | ||
}; | ||
console.log('axios.get', url, params); | ||
const resp = await this.axiosInstance.get(url, { params }); | ||
|
||
return resp.data.rows | ||
.map((row: any): RemotePlace => { | ||
const nameData = row.key[1]; | ||
return { | ||
id: row.id, | ||
name: nameData.substring('name:'.length), | ||
lineage: extractLineage(row.doc), | ||
type: 'remote', | ||
}; | ||
}); | ||
return resp.data.rows.map((row: any) => row.doc); | ||
}; | ||
|
||
getDoc = async (id: string): Promise<any> => { | ||
|
@@ -228,10 +206,3 @@ function minify(doc: any): any { | |
}; | ||
} | ||
|
||
function extractLineage(doc: any): string[] { | ||
if (doc?.parent?._id) { | ||
return [doc.parent._id, ...extractLineage(doc.parent)]; | ||
} | ||
|
||
return []; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
import Place from '../services/place'; | ||
import { ChtApi, RemotePlace } from './cht-api'; | ||
import { ChtApi } from './cht-api'; | ||
import { IPropertyValue } from '../property-value'; | ||
import { ContactType, HierarchyConstraint } from '../config'; | ||
import { NamePropertyValue } from '../property-value/name-property-value'; | ||
|
||
type RemotePlacesByType = { | ||
[key: string]: RemotePlace[]; | ||
|
@@ -9,18 +12,36 @@ type RemotePlaceDatastore = { | |
[key: string]: RemotePlacesByType; | ||
}; | ||
|
||
export type RemotePlace = { | ||
id: string; | ||
name: IPropertyValue; | ||
lineage: string[]; | ||
ambiguities?: RemotePlace[]; | ||
|
||
// sadly, sometimes invalid or uncreated objects "pretend" to be remote | ||
// should reconsider this naming | ||
type: 'remote' | 'local' | 'invalid'; | ||
}; | ||
|
||
export default class RemotePlaceCache { | ||
private static cache: RemotePlaceDatastore = {}; | ||
|
||
public static async getPlacesWithType(chtApi: ChtApi, placeType: string) | ||
public static async getPlacesWithType(chtApi: ChtApi, contactType: ContactType, hierarchyLevel: HierarchyConstraint) | ||
: Promise<RemotePlace[]> { | ||
const domainStore = await RemotePlaceCache.getDomainStore(chtApi, placeType); | ||
const domainStore = await RemotePlaceCache.getDomainStore(chtApi, contactType, hierarchyLevel); | ||
return domainStore; | ||
} | ||
|
||
public static async add(place: Place, chtApi: ChtApi): Promise<void> { | ||
const domainStore = await RemotePlaceCache.getDomainStore(chtApi, place.type.name); | ||
domainStore.push(place.asRemotePlace()); | ||
public static add(place: Place, chtApi: ChtApi): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we can figure out if a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason we need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes i understand that, the reason i bring it up is the comments here
I noticed we can convert a Place to a Remote place even when it's not created on the server. I think it'd make sense to treat remote places strictly as places we fetched from the server then we could rid of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I've tried this, and it's a bit complex. Mostly because you need the configuration object when convertingto get data from the Place into a similar structure that comes in RemotePlace. I spent like an entire day on it and I think you're right that what we have here is still unsatisfying. I suspect I think this is probably a lot of code churn and this PR is already large + is the base of #20. Doing that work here would cause a complex merge. Would you mind if we address it in a follow-up ticket? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @freddieptf Thoughts? Do you think it is important enough to do it in this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think that can be addressed in another PR |
||
const { domain } = chtApi.chtSession.authInfo; | ||
const placeType = place.type.name; | ||
|
||
const places = RemotePlaceCache.cache[domain]?.[placeType]; | ||
// if there is no cache existing, discard the value | ||
// it will be fetched if needed when the cache is built | ||
if (places) { | ||
places.push(place.asRemotePlace()); | ||
} | ||
} | ||
|
||
public static clear(chtApi: ChtApi, contactTypeName?: string): void { | ||
|
@@ -34,14 +55,14 @@ export default class RemotePlaceCache { | |
} | ||
} | ||
|
||
private static async getDomainStore(chtApi: ChtApi, placeType: string) | ||
private static async getDomainStore(chtApi: ChtApi, contactType: ContactType, hierarchyLevel: HierarchyConstraint) | ||
: Promise<RemotePlace[]> { | ||
const { domain } = chtApi.chtSession.authInfo; | ||
const placeType = hierarchyLevel.contact_type; | ||
const { cache: domainCache } = RemotePlaceCache; | ||
|
||
const places = domainCache[domain]?.[placeType]; | ||
if (!places) { | ||
const fetchPlacesWithType = chtApi.getPlacesWithType(placeType); | ||
const fetchPlacesWithType = RemotePlaceCache.fetchRemotePlaces(chtApi, contactType, hierarchyLevel); | ||
domainCache[domain] = { | ||
...domainCache[domain], | ||
[placeType]: await fetchPlacesWithType, | ||
|
@@ -50,4 +71,22 @@ export default class RemotePlaceCache { | |
|
||
return domainCache[domain][placeType]; | ||
} | ||
|
||
private static async fetchRemotePlaces(chtApi: ChtApi, contactType: ContactType, hierarchyLevel: HierarchyConstraint): Promise<RemotePlace[]> { | ||
function extractLineage(doc: any): string[] { | ||
if (doc?.parent) { | ||
return [doc.parent._id, ...extractLineage(doc.parent)]; | ||
} | ||
|
||
return []; | ||
} | ||
|
||
const docs = await chtApi.getPlacesWithType(hierarchyLevel.contact_type); | ||
return docs.map((doc: any): RemotePlace => ({ | ||
id: doc._id, | ||
name: new NamePropertyValue(doc.name, hierarchyLevel), | ||
lineage: extractLineage(doc), | ||
type: 'remote', | ||
})); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to the PR but nothing hints that
getPropertyWithName
might actually throw an error which i think makes it hard to reason about error handling in functions that call it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true. now that we assert it on startup, maybe we don't need it in the property? we know it is also there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah i think it would be clearer if
getPropertyWithName
returned a string or undefined; then the caller can choose to assert if undefined and throw if neededThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we assert at startup, we can guarantee it is never undefined at runtime
this allows us to safely make it a
string
which takes the burden off the calling codei personally like this since it leads to cleaner calling code. would you rather i not assert in the get and just use typescript operators to cast?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't we get a runtime error if the property name that was being accessed was not asserted at start up like
name
is? If this function is only used for gettingname
then maybe it can be renamed and args changed?