-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
510 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { helper } from '@ember/component/helper'; | ||
|
||
export function includes([array, value]: [unknown[], unknown]): boolean { | ||
return array.includes(value); | ||
} | ||
|
||
export default helper(includes); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import Modifier from 'ember-modifier'; | ||
import bb, { line, subchart } from 'billboard.js'; | ||
|
||
interface MetricsChartArgs { | ||
positional: []; | ||
named: { | ||
dataColumns?: Array<Array<string|number>>, | ||
dataRows?: Array<Array<string|number>>, | ||
}; | ||
} | ||
|
||
export default class MetricsChart extends Modifier<MetricsChartArgs> { | ||
chart: any = null; | ||
|
||
didReceiveArguments() { | ||
if (this.chart) { | ||
this.chart.destroy(); | ||
} | ||
this.chart = bb.generate({ | ||
bindto: this.element, | ||
data: { | ||
type: line(), | ||
x: 'report_date', | ||
// columns: this.args.named.dataColumns, | ||
rows: this.args.named.dataRows, | ||
}, | ||
axis: { | ||
x: { | ||
type: 'timeseries', | ||
tick: { | ||
format: '%Y-%m-%d', | ||
}, | ||
}, | ||
}, | ||
subchart: { | ||
show: subchart(), | ||
showHandle: true, | ||
}, | ||
tooltip: { | ||
grouped: false, | ||
linked: true, | ||
}, | ||
}); | ||
} | ||
|
||
willRemove() { | ||
if (this.chart) { | ||
this.chart.destroy(); | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<LoadingIndicator /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import Controller from '@ember/controller'; | ||
import { action, get } from '@ember/object'; | ||
import { tracked } from '@glimmer/tracking'; | ||
import { MetricsReportAttrs } from './route'; | ||
|
||
|
||
type ReportFields = { | ||
keywordFields: string[], | ||
numericFields: string[], | ||
}; | ||
|
||
|
||
function gatherFields(obj: any): ReportFields { | ||
const keywordFields: string[] = [] | ||
const numericFields: string[] = [] | ||
for (const fieldName in obj) { | ||
if (fieldName === 'report_date' || fieldName === 'timestamp') { | ||
continue; | ||
} | ||
const fieldValue = obj[fieldName]; | ||
switch (typeof fieldValue) { | ||
case 'string': | ||
keywordFields.push(fieldName); | ||
break; | ||
case 'number': | ||
numericFields.push(fieldName); | ||
break; | ||
case 'object': | ||
const nestedFields = gatherFields(fieldValue); | ||
keywordFields.push(...nestedFields.keywordFields.map( | ||
nestedFieldName => `${fieldName}.${nestedFieldName}`, | ||
)); | ||
numericFields.push(...nestedFields.numericFields.map( | ||
nestedFieldName => `${fieldName}.${nestedFieldName}`, | ||
)); | ||
break; | ||
default: | ||
console.log(`ignoring unexpected ${fieldName}: ${fieldValue}`) | ||
} | ||
} | ||
return { | ||
keywordFields, | ||
numericFields, | ||
}; | ||
} | ||
|
||
|
||
export default class MetricsReportDetailController extends Controller { | ||
queryParams = [ | ||
{ daysBack: { scope: 'controller' as const } }, | ||
'yFields', | ||
'xGroupField', | ||
'xGroupFilter', | ||
] | ||
|
||
@tracked daysBack: string = '13'; | ||
@tracked model: MetricsReportAttrs[] = []; | ||
@tracked yFields: string[] = []; | ||
@tracked xGroupField?: string; | ||
@tracked xField: string = 'report_date'; | ||
@tracked xGroupFilter: string = ''; | ||
|
||
get reportFields(): ReportFields { | ||
const aReport: MetricsReportAttrs = this.model![0]; | ||
return gatherFields(aReport); | ||
} | ||
|
||
get chartRows(): Array<Array<string|number|null>>{ | ||
if (!this.xGroupField) { | ||
const fieldNames = [this.xField, ...this.yFields]; | ||
const rows = this.model.map( | ||
datum => fieldNames.map( | ||
fieldName => (get(datum, fieldName) as string | number | undefined) ?? null, | ||
), | ||
); | ||
return [fieldNames, ...rows]; | ||
} | ||
const groupedFieldNames = new Set<string>(); | ||
const rowsByX: any = {}; | ||
for (const datum of this.model) { | ||
const xValue = get(datum, this.xField) as string; | ||
if (!rowsByX[xValue]) { | ||
rowsByX[xValue] = {}; | ||
} | ||
const groupName = get(datum, this.xGroupField) as string; | ||
if (!this.xGroupFilter || groupName.includes(this.xGroupFilter)) { | ||
this.yFields.forEach(fieldName => { | ||
const groupedField = `${groupName} ${fieldName}`; | ||
groupedFieldNames.add(groupedField); | ||
const fieldValue = get(datum, fieldName); | ||
rowsByX[xValue][groupedField] = fieldValue; | ||
}); | ||
} | ||
} | ||
const rows = Object.entries(rowsByX).map( | ||
([xValue, rowData]: [string, any]) => { | ||
const yValues = [...groupedFieldNames].map( | ||
groupedFieldName => (rowData[groupedFieldName] as string | number | undefined) ?? null, | ||
); | ||
return [xValue, ...yValues]; | ||
}, | ||
); | ||
return [ | ||
[this.xField, ...groupedFieldNames], | ||
...rows, | ||
]; | ||
} | ||
|
||
@action | ||
yFieldToggle(fieldName: string) { | ||
if (this.yFields.includes(fieldName)) { | ||
this.yFields = this.yFields.filter(f => f !== fieldName); | ||
} else { | ||
this.yFields = [...this.yFields, fieldName]; | ||
} | ||
} | ||
} | ||
|
||
declare module '@ember/controller' { | ||
interface Registry { | ||
'osf-metrics.report-detail': MetricsReportDetailController; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import Route from '@ember/routing/route'; | ||
import config from 'ember-get-config'; | ||
|
||
const { | ||
OSF: { | ||
apiUrl, | ||
}, | ||
} = config; | ||
|
||
export interface MetricsReportAttrs { | ||
report_date: string, // YYYY-MM-DD | ||
[attr: string]: string | number | object, | ||
} | ||
|
||
interface MetricsReport { | ||
id: string; | ||
type: string; | ||
attributes: MetricsReportAttrs; | ||
} | ||
|
||
interface RecentMetricsReportResponse { | ||
data: MetricsReport[]; | ||
} | ||
|
||
export default class OsfMetricsRoute extends Route { | ||
queryParams = { | ||
daysBack: { | ||
refreshModel: true, | ||
}, | ||
yFields: { | ||
replace: true, | ||
}, | ||
xGroupField: { | ||
replace: true, | ||
}, | ||
xGroupFilter: { | ||
replace: true, | ||
}, | ||
} | ||
|
||
async model(params: { daysBack: string, reportName?: string }) { | ||
const url = `${apiUrl}/_/metrics/reports/${params.reportName}/recent/?days_back=${params.daysBack}` | ||
const response = await fetch(url); | ||
const responseJson: RecentMetricsReportResponse = await response.json(); | ||
return responseJson.data.map(datum => datum.attributes); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<p> | ||
days back | ||
{{#each (array '7' '13' '31' '73' '371') as |daysBackOption|}} | ||
| <LinkTo @query={{hash daysBack=daysBackOption}}>{{daysBackOption}}</LinkTo> | ||
{{/each}} | ||
</p> | ||
<p> | ||
data fields (y-axis) | ||
{{#each this.reportFields.numericFields as |fieldName|}} | ||
| <label> | ||
<Input | ||
@type='checkbox' | ||
@checked={{includes this.yFields fieldName}} | ||
{{on 'input' (fn this.yFieldToggle fieldName)}} | ||
/> | ||
{{fieldName}} | ||
</label> | ||
{{/each}} | ||
</p> | ||
{{#if this.reportFields.keywordFields.length}} | ||
<p> | ||
group by | ||
<PowerSelect | ||
@options={{this.reportFields.keywordFields}} | ||
@selected={{this.xGroupField}} | ||
@onChange={{fn (mut this.xGroupField)}} | ||
as |item| | ||
> | ||
{{item}} | ||
</PowerSelect> | ||
</p> | ||
<p> | ||
<label> | ||
filter groups | ||
<Input @type='text' @value={{mut this.xGroupFilter}} /> | ||
</label> | ||
</p> | ||
{{/if}} | ||
{{#if (and this.model.length this.yFields.length)}} | ||
<section> | ||
<div {{metrics-chart dataRows=this.chartRows}}></div> | ||
</section> | ||
{{/if}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import Route from '@ember/routing/route'; | ||
import config from 'ember-get-config'; | ||
|
||
const { | ||
OSF: { | ||
apiUrl, | ||
}, | ||
} = config; | ||
|
||
interface MetricsReportName { | ||
id: string; | ||
type: 'metrics-report-name'; | ||
links: { | ||
recent: string, | ||
}; | ||
} | ||
|
||
interface MetricsReportNameResponse { | ||
data: MetricsReportName[]; | ||
} | ||
|
||
export default class OsfMetricsRoute extends Route { | ||
async model() { | ||
const url = `${apiUrl}/_/metrics/reports/`; | ||
const response = await fetch(url); | ||
const responseJson: MetricsReportNameResponse = await response.json(); | ||
return responseJson.data.map(metricsReport => metricsReport.id); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
.OsfMetrics { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
|
||
.OsfMetrics > p { | ||
max-width: 62vw; | ||
} | ||
|
||
.OsfMetrics > section { | ||
width: 87vw; | ||
} | ||
|
||
.OsfMetrics :global(.active) { | ||
font-weight: bold; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{{page-title 'osf metrics'}} | ||
<section local-class='OsfMetrics'> | ||
<h1>osf metrics</h1> | ||
<p> | ||
reports | ||
{{#each @model as |reportName|}} | ||
| | ||
<LinkTo @route='osf-metrics.report-detail' @model={{reportName}}>{{reportName}}</LinkTo> | ||
{{/each}} | ||
</p> | ||
{{outlet}} | ||
</section> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.