-
Notifications
You must be signed in to change notification settings - Fork 3
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
Create reactive datastore for models with Mobx #277
Changes from all commits
cc4a620
ed3357f
3f0b4e7
d0a8f75
f658eb8
f9f2b62
87df75e
5863335
c4eea75
d2f36be
06551f4
17ac117
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 |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Recidiviz - a data platform for criminal justice reform | ||
// Copyright (C) 2020 Recidiviz, Inc. | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
// ============================================================================= | ||
|
||
import { autorun } from "mobx"; | ||
import Tenant from "../contentModels/Tenant"; | ||
import RootStore from "./RootStore"; | ||
|
||
let DataStore: RootStore; | ||
|
||
/** | ||
* Convenience method to run an immediate, one-time reactive effect | ||
*/ | ||
function reactImmediately(effect: () => void) { | ||
// this will call the effect function immediately, | ||
// and then immediately call the disposer to tear down the reaction | ||
autorun(effect)(); | ||
} | ||
|
||
beforeEach(() => { | ||
DataStore = new RootStore(); | ||
}); | ||
|
||
test("contains a tenant store", () => { | ||
expect(DataStore.tenantStore).toBeDefined(); | ||
}); | ||
|
||
describe("tenant store", () => { | ||
let tenantStore: typeof DataStore.tenantStore; | ||
|
||
beforeEach(() => { | ||
tenantStore = DataStore.tenantStore; | ||
}); | ||
|
||
test("has no default tenant", () => { | ||
reactImmediately(() => { | ||
expect(tenantStore.currentTenant).toBeUndefined(); | ||
}); | ||
expect.hasAssertions(); | ||
}); | ||
|
||
test("can set current tenant", () => { | ||
tenantStore.setCurrentTenant({ tenantId: "US_ND" }); | ||
reactImmediately(() => { | ||
expect(tenantStore.currentTenant).toBeInstanceOf(Tenant); | ||
}); | ||
expect.hasAssertions(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Recidiviz - a data platform for criminal justice reform | ||
// Copyright (C) 2020 Recidiviz, Inc. | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
// ============================================================================= | ||
|
||
import TenantStore from "./TenantStore"; | ||
|
||
export default class RootStore { | ||
tenantStore: TenantStore; | ||
|
||
constructor() { | ||
this.tenantStore = new TenantStore({ rootStore: this }); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Recidiviz - a data platform for criminal justice reform | ||
// Copyright (C) 2020 Recidiviz, Inc. | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
// ============================================================================= | ||
|
||
import { makeAutoObservable } from "mobx"; | ||
import { TenantId } from "../contentApi/types"; | ||
import Tenant, { createTenant } from "../contentModels/Tenant"; | ||
import type RootStore from "./RootStore"; | ||
|
||
export default class TenantStore { | ||
currentTenant?: Tenant; | ||
|
||
rootStore: RootStore; | ||
|
||
constructor({ rootStore }: { rootStore: RootStore }) { | ||
makeAutoObservable(this, { rootStore: false }); | ||
|
||
this.rootStore = rootStore; | ||
} | ||
|
||
setCurrentTenant(opts: { tenantId: TenantId }): void { | ||
this.currentTenant = createTenant(opts); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Recidiviz - a data platform for criminal justice reform | ||
// Copyright (C) 2020 Recidiviz, Inc. | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
// ============================================================================= | ||
|
||
import RootStore from "./RootStore"; | ||
|
||
export default new RootStore(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
// ============================================================================= | ||
|
||
import assertNever from "assert-never"; | ||
import { makeAutoObservable, runInAction } from "mobx"; | ||
import { MetricTypeIdList, TenantContent, TenantId } from "../contentApi/types"; | ||
import { | ||
DemographicsByCategoryRecord, | ||
|
@@ -70,23 +71,23 @@ type InitOptions<RecordFormat> = { | |
*/ | ||
export default class Metric<RecordFormat extends AnyRecord> { | ||
// metadata properties | ||
description: string; | ||
readonly description: string; | ||
|
||
methodology: string; | ||
readonly methodology: string; | ||
|
||
name: string; | ||
readonly name: string; | ||
|
||
// relationships | ||
collections: CollectionMap = new Map(); | ||
|
||
// we don't really need the entire Tenant object, | ||
// only the ID for use in the API request | ||
tenantId: TenantId; | ||
readonly tenantId: TenantId; | ||
|
||
// data properties | ||
private dataTransformer: DataTransformer<RecordFormat>; | ||
private readonly dataTransformer: DataTransformer<RecordFormat>; | ||
|
||
private sourceFileName: string; | ||
private readonly sourceFileName: string; | ||
|
||
isLoading?: boolean; | ||
|
||
|
@@ -102,6 +103,18 @@ export default class Metric<RecordFormat extends AnyRecord> { | |
dataTransformer, | ||
sourceFileName, | ||
}: InitOptions<RecordFormat>) { | ||
makeAutoObservable(this, { | ||
// readonly properties do not need to be observed | ||
description: false, | ||
methodology: false, | ||
name: false, | ||
tenantId: false, | ||
// in practice, collections should not change once we are done | ||
// boostrapping them (which is done right after instantiation); | ||
// no need to make them observable | ||
collections: false, | ||
}); | ||
|
||
// initialize metadata | ||
this.name = name; | ||
this.description = description; | ||
|
@@ -119,13 +132,15 @@ export default class Metric<RecordFormat extends AnyRecord> { | |
metricNames: [this.sourceFileName], | ||
tenantId: this.tenantId, | ||
}); | ||
if (apiResponse) { | ||
const metricFileData = apiResponse[this.sourceFileName]; | ||
if (metricFileData) { | ||
this.allRecords = this.dataTransformer(metricFileData); | ||
runInAction(() => { | ||
if (apiResponse) { | ||
const metricFileData = apiResponse[this.sourceFileName]; | ||
if (metricFileData) { | ||
this.allRecords = this.dataTransformer(metricFileData); | ||
} | ||
this.isLoading = false; | ||
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'm trying to wrap my head around when it would make sense to make a store vs making observable attributes on a model like you've done here. It makes sense that you would want 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. Is your intention to eventually store the metric data as part 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 did it this way because the relationship between tenant and metric is strictly hierarchical ... the Mobx docs advise against normalizing those kinds of relationships or making your stores resemble relational database tables or something like that. Instead the metrics are literally nested within As to your second question, it's a good question ... I don't really know the answer yet. There are some places where a |
||
} | ||
this.isLoading = false; | ||
} | ||
}); | ||
} | ||
|
||
get records(): RecordFormat[] | undefined { | ||
|
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.
Great tests. Smart to use
when
reaction for these tests!