-
Notifications
You must be signed in to change notification settings - Fork 0
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
[Agency Dashboard] Create agency data store to fetch published data #189
Changes from 3 commits
5239e8e
0b20ee7
909bba6
e5f2dde
0454c4a
4ea8f76
6e7d853
aa498c3
804ac03
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,85 @@ | ||
// Recidiviz - a data platform for criminal justice reform | ||
// Copyright (C) 2022 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 { Metric } from "@justice-counts/common/types"; | ||
import { makeAutoObservable, runInAction } from "mobx"; | ||
|
||
import { request } from "../utils/networking"; | ||
|
||
class AgencyDataStore { | ||
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 calling it AgencyDataStore because in the future this will include not just an agency's published metric datapoints and metric information but also agency metadata such as name, description etc 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. Oh I love that! Good thinking |
||
metrics: Metric[]; | ||
|
||
loading: boolean; | ||
|
||
constructor() { | ||
makeAutoObservable(this); | ||
this.metrics = []; | ||
this.loading = true; | ||
} | ||
|
||
get metricKeyToDisplayName(): { [metricKey: string]: string | null } { | ||
const mapping: { [metricKey: string]: string | null } = {}; | ||
this.metrics.forEach((metric) => { | ||
mapping[metric.key] = metric.display_name; | ||
}); | ||
return mapping; | ||
} | ||
|
||
get metricDisplayNameToKey(): { [displayName: string]: string } { | ||
const mapping: { [displayName: string]: string } = {}; | ||
this.metrics.forEach((metric) => { | ||
mapping[metric.display_name] = metric.key; | ||
}); | ||
return mapping; | ||
} | ||
|
||
async fetchAgencyData(agencyId: number): Promise<void | Error> { | ||
try { | ||
const response = (await request({ | ||
path: `/api/agencies/${agencyId}/published_data`, | ||
method: "GET", | ||
})) as Response; | ||
if (response.status === 200) { | ||
const result = await response.json(); | ||
runInAction(() => { | ||
this.metrics = result; | ||
}); | ||
} else { | ||
const error = await response.json(); | ||
throw new Error(error.description); | ||
} | ||
runInAction(() => { | ||
this.loading = false; | ||
}); | ||
} catch (error) { | ||
runInAction(() => { | ||
this.loading = false; | ||
}); | ||
throw error; | ||
} | ||
} | ||
|
||
resetState() { | ||
// reset the state | ||
runInAction(() => { | ||
this.metrics = []; | ||
this.loading = true; | ||
}); | ||
} | ||
} | ||
|
||
export default AgencyDataStore; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,101 +16,16 @@ | |
// ============================================================================= | ||
|
||
import BaseDatapointsStore from "@justice-counts/common/stores/BaseDatapointsStore"; | ||
import { | ||
DatapointsByMetric, | ||
DataVizAggregateName, | ||
DimensionNamesByMetricAndDisaggregation, | ||
RawDatapoint, | ||
} from "@justice-counts/common/types"; | ||
import { isPositiveNumber } from "@justice-counts/common/utils"; | ||
import { makeObservable, override, runInAction } from "mobx"; | ||
|
||
import { request } from "../utils/networking"; | ||
|
||
class DatapointsStore extends BaseDatapointsStore { | ||
rawDatapoints: RawDatapoint[]; | ||
|
||
dimensionNamesByMetricAndDisaggregation: DimensionNamesByMetricAndDisaggregation; | ||
|
||
loading: boolean; | ||
|
||
constructor() { | ||
super(); | ||
makeObservable(this, { | ||
getDatapoints: override, | ||
}); | ||
this.rawDatapoints = []; | ||
this.dimensionNamesByMetricAndDisaggregation = {}; | ||
this.loading = true; | ||
} | ||
|
||
get metricKeyToDisplayName(): { [metricKey: string]: string | null } { | ||
const mapping: { [metricKey: string]: string | null } = {}; | ||
this.rawDatapoints.forEach((dp) => { | ||
mapping[dp.metric_definition_key] = dp.metric_display_name; | ||
}); | ||
return mapping; | ||
} | ||
|
||
/** | ||
* Transforms raw data from the server into Datapoints keyed by metric, | ||
* grouped by aggregate values and disaggregations. | ||
* Aggregate is an array of objects each containing start_date, end_date, and the aggregate value. | ||
* Disaggregations are keyed by disaggregation name and each value is an object | ||
* with the key being the start_date and the value being an object | ||
* containing start_date, end_date and key value pairs for each dimension and their values. | ||
* See the DatapointsByMetric type for details. | ||
*/ | ||
get datapointsByMetric(): DatapointsByMetric { | ||
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. This is already inherited from BaseDatapointsStore, wonder how this got here |
||
return this.rawDatapoints.reduce((res: DatapointsByMetric, dp) => { | ||
if (!res[dp.metric_definition_key]) { | ||
res[dp.metric_definition_key] = { | ||
aggregate: [], | ||
disaggregations: {}, | ||
}; | ||
} | ||
|
||
const sanitizedValue = | ||
dp.value !== null && isPositiveNumber(dp.value) | ||
? Number(dp.value) | ||
: null; | ||
|
||
if ( | ||
dp.disaggregation_display_name === null || | ||
dp.dimension_display_name === null | ||
) { | ||
res[dp.metric_definition_key].aggregate.push({ | ||
[DataVizAggregateName]: sanitizedValue, | ||
start_date: dp.start_date, | ||
end_date: dp.end_date, | ||
frequency: dp.frequency, | ||
dataVizMissingData: 0, | ||
}); | ||
} else { | ||
if ( | ||
!res[dp.metric_definition_key].disaggregations[ | ||
dp.disaggregation_display_name | ||
] | ||
) { | ||
res[dp.metric_definition_key].disaggregations[ | ||
dp.disaggregation_display_name | ||
] = {}; | ||
} | ||
res[dp.metric_definition_key].disaggregations[ | ||
dp.disaggregation_display_name | ||
][dp.start_date] = { | ||
...res[dp.metric_definition_key].disaggregations[ | ||
dp.disaggregation_display_name | ||
][dp.start_date], | ||
start_date: dp.start_date, | ||
end_date: dp.end_date, | ||
[dp.dimension_display_name]: sanitizedValue, | ||
frequency: dp.frequency, | ||
dataVizMissingData: 0, | ||
}; | ||
} | ||
return res; | ||
}, {}); | ||
} | ||
|
||
async getDatapoints(agencyId: number): Promise<void | Error> { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,7 +43,6 @@ abstract class DatapointsStore { | |
rawDatapoints: observable, | ||
dimensionNamesByMetricAndDisaggregation: observable, | ||
loading: observable, | ||
metricKeyToDisplayName: computed, | ||
datapointsByMetric: computed, | ||
getDatapoints: action, | ||
resetState: action, | ||
|
@@ -53,14 +52,6 @@ abstract class DatapointsStore { | |
this.loading = true; | ||
} | ||
|
||
get metricKeyToDisplayName(): { [metricKey: string]: string | null } { | ||
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. use agency data store's instead |
||
const mapping: { [metricKey: string]: string | null } = {}; | ||
this.rawDatapoints.forEach((dp) => { | ||
mapping[dp.metric_definition_key] = dp.metric_display_name; | ||
}); | ||
return mapping; | ||
} | ||
|
||
/** | ||
* Transforms raw data from the server into Datapoints keyed by metric, | ||
* grouped by aggregate values and disaggregations. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,10 +16,6 @@ | |
// ============================================================================= | ||
|
||
import BaseDatapointsStore from "@justice-counts/common/stores/BaseDatapointsStore"; | ||
import { | ||
DimensionNamesByMetricAndDisaggregation, | ||
RawDatapoint, | ||
} from "@justice-counts/common/types"; | ||
import { | ||
IReactionDisposer, | ||
makeObservable, | ||
|
@@ -37,12 +33,6 @@ class DatapointsStore extends BaseDatapointsStore { | |
|
||
api: API; | ||
|
||
rawDatapoints: RawDatapoint[]; | ||
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. Just curious, why is it safe to get rid of these fields now? 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. They're already inherited from the BaseDatapointsStore! I'm not sure how those got in there if either I missed them or they got added back in |
||
|
||
dimensionNamesByMetricAndDisaggregation: DimensionNamesByMetricAndDisaggregation; | ||
|
||
loading: boolean; | ||
|
||
disposers: IReactionDisposer[] = []; | ||
|
||
constructor(userStore: UserStore, api: API) { | ||
|
@@ -57,9 +47,6 @@ class DatapointsStore extends BaseDatapointsStore { | |
|
||
this.api = api; | ||
this.userStore = userStore; | ||
this.rawDatapoints = []; | ||
this.dimensionNamesByMetricAndDisaggregation = {}; | ||
this.loading = true; | ||
|
||
this.disposers.push( | ||
reaction( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -188,7 +188,6 @@ class MetricConfigStore { | |
} | ||
|
||
const metrics: Metric[] = await response.json(); | ||
|
||
return metrics; | ||
}; | ||
|
||
|
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.
nit: maybe
selectedMetricKey
?