mirror of
https://github.com/datahub-project/datahub.git
synced 2025-11-01 03:09:12 +00:00
203 lines
7.4 KiB
TypeScript
203 lines
7.4 KiB
TypeScript
import Component from '@ember/component';
|
|
import { get, computed, setProperties, getProperties } from '@ember/object';
|
|
import { task } from 'ember-concurrency';
|
|
import ComputedProperty from '@ember/object/computed';
|
|
import { IChartDatum } from 'wherehows-web/typings/app/visualization/charts';
|
|
import { IHealthScore, IDatasetHealth } from 'wherehows-web/typings/api/datasets/health';
|
|
import { readDatasetHealthByUrn, getCategory } from 'wherehows-web/utils/api/datasets/health';
|
|
import { Tabs } from 'wherehows-web/constants/datasets/shared';
|
|
import { equal } from '@ember-decorators/object/computed';
|
|
import { IObject } from 'wherehows-web/typings/generic';
|
|
|
|
/**
|
|
* Used for the dataset health tab, represents the fieldnames for the health score table
|
|
*/
|
|
export enum HealthDataFields {
|
|
category = 'Category',
|
|
severity = 'Severity',
|
|
description = 'Description',
|
|
score = 'Score'
|
|
}
|
|
|
|
/**
|
|
* This is the container component for the dataset health tab. It should contain the health bar graphs and a table
|
|
* depicting the detailed health scores. Aside from fetching the data, it also handles click interactions between
|
|
* the graphs and the table in terms of filtering and displaying of data
|
|
*/
|
|
export default class DatasetHealthContainer extends Component {
|
|
/**
|
|
* The urn identifier for the dataset
|
|
* @type {string}
|
|
*/
|
|
urn: string;
|
|
|
|
/**
|
|
* Sets the classes for the rendered html element for the component
|
|
* @type {Array<string>}
|
|
*/
|
|
classNames = ['dataset-health'];
|
|
|
|
/**
|
|
* The current filter for the category chart. Clicking on a bar in the chart changes this value and will cause
|
|
* the table component to filter out certain rows. Since we only allow one filter at a time, only this or
|
|
* currentSeverityFilter should have a truthy value at any given point.
|
|
* @type {string}
|
|
*/
|
|
currentCategoryFilter = '';
|
|
|
|
/**
|
|
* The current filter for the severity chart. Clicking on a bar in the chart changes this value and will cause
|
|
* the table component to filter out certain rows. Since we only allow one filter at a time, only this or
|
|
* currentCategoryFilter should have a truthy value at any given point.
|
|
* @type {string}
|
|
*/
|
|
currentSeverityFilter = '';
|
|
|
|
/**
|
|
* Raw fetched data for the category metrics
|
|
* @type {Array<pending>}
|
|
*/
|
|
categoryMetrics: Array<IChartDatum> = [];
|
|
|
|
/**
|
|
* Raw fetched data for the category metrics
|
|
* @type {Array<pending>}
|
|
*/
|
|
severityMetrics: Array<IChartDatum> = [];
|
|
|
|
/**
|
|
* Fetched data for the health score detailed data.
|
|
* @type {Array<IHealthScore>}
|
|
*/
|
|
tableData: Array<IHealthScore> = [];
|
|
|
|
/**
|
|
* Passed in from the higher level component, we use this property in order to determine whether the dataset health
|
|
* tab is the currently selected tab
|
|
* @type {Tabs}
|
|
*/
|
|
tabSelected: Tabs;
|
|
|
|
/**
|
|
* Calculated from the currently selected tab to determine whether this container is the currently selected tab.
|
|
* Note: Highcharts calculates size and other chart details upon initial render and doesn't do a good job of handling
|
|
* rerenders. Because of this we want those calculations to take place when dataset health is the currently active tab,
|
|
* otherwise we will insert elements off screen and size will default to 0 and we lose our charts
|
|
* @type {ComputedProperty<boolean>}
|
|
*/
|
|
@equal('tabSelected', Tabs.Health)
|
|
isActiveTab: boolean;
|
|
|
|
/**
|
|
* Modified categoryMetrics to add properties that will help us render our actual charts without modifying the original
|
|
* data
|
|
* @type {ComputedProperty<Array<IChartDatum>>}
|
|
*/
|
|
renderedCategories: ComputedProperty<Array<IChartDatum>> = computed(
|
|
'categoryMetrics',
|
|
'currentCategoryFilter',
|
|
function(this: DatasetHealthContainer): Array<IChartDatum> {
|
|
const { categoryMetrics, currentCategoryFilter } = getProperties(
|
|
this,
|
|
'categoryMetrics',
|
|
'currentCategoryFilter'
|
|
);
|
|
|
|
return categoryMetrics.map(category => ({
|
|
...category,
|
|
isFaded: !!currentCategoryFilter && category.name !== currentCategoryFilter
|
|
}));
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Modified severityMetrics to add properties that will help us render our actual charts
|
|
* @type {ComputedProperty<Array<IChartDatum>>}
|
|
*/
|
|
renderedSeverity: ComputedProperty<Array<IChartDatum>> = computed(
|
|
'severityMetrics',
|
|
'currentSeverityFilter',
|
|
function(this: DatasetHealthContainer): Array<IChartDatum> {
|
|
const { severityMetrics, currentSeverityFilter } = getProperties(
|
|
this,
|
|
'severityMetrics',
|
|
'currentSeverityFilter'
|
|
);
|
|
|
|
return severityMetrics.map(severity => ({
|
|
...severity,
|
|
isFaded: !!currentSeverityFilter && severity.name !== currentSeverityFilter,
|
|
customColorClass: `severity-chart__bar--${severity.name.toLowerCase()}`
|
|
}));
|
|
}
|
|
);
|
|
|
|
didInsertElement() {
|
|
get(this, 'getContainerDataTask').perform();
|
|
}
|
|
|
|
didUpdateAttrs() {
|
|
get(this, 'getContainerDataTask').perform();
|
|
}
|
|
|
|
/**
|
|
* An async parent task to group all data tasks for this container component
|
|
* @type {Task<TaskInstance<Promise<any>>, (a?: any) => TaskInstance<TaskInstance<Promise<any>>>>}
|
|
*/
|
|
getContainerDataTask = task(function*(this: DatasetHealthContainer): IterableIterator<Promise<IDatasetHealth>> {
|
|
const health: IDatasetHealth = yield readDatasetHealthByUrn(get(this, 'urn'));
|
|
|
|
const details = health.validations || [];
|
|
const total = details.length;
|
|
const categories: IObject<number> = {};
|
|
const severities: IObject<number> = {};
|
|
|
|
// Go through the details and find the COUNT of severity and category groupings
|
|
const tableData: Array<IHealthScore> = details.map(detail => {
|
|
const category = getCategory(detail.validator);
|
|
const severity = detail.tier || 'none';
|
|
categories[category] = (categories[category] || 0) + 1;
|
|
severities[severity] = (severities[severity] || 0) + 1;
|
|
|
|
return { category, severity, description: detail.description, score: detail.score * 100 };
|
|
});
|
|
|
|
const categoryMetrics: Array<IChartDatum> = Object.keys(categories).map(category => ({
|
|
name: category,
|
|
value: Math.round((categories[category] / total) * 100)
|
|
}));
|
|
|
|
const severityMetrics: Array<IChartDatum> = Object.keys(severities).map(severity => ({
|
|
name: severity,
|
|
value: Math.round((severities[severity] / total) * 100)
|
|
}));
|
|
|
|
setProperties(this, {
|
|
categoryMetrics,
|
|
severityMetrics,
|
|
tableData
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Triggered when the user clicks on one of the bars in the summary charts child component, will trigger
|
|
* a filter for whatever bar they select, unless it already is one in which case we will remove the filter
|
|
* @param this - Explicit this declaration for typescript
|
|
* @param filterType - Whether we are filtering by category or severity
|
|
* @param filterDatum - Passed in to the action by the child component, contains the tag to be filtered for
|
|
*/
|
|
onFilterSelect(this: DatasetHealthContainer, filterType: string, filterDatum: IChartDatum): void {
|
|
const { currentCategoryFilter, currentSeverityFilter } = getProperties(
|
|
this,
|
|
'currentCategoryFilter',
|
|
'currentSeverityFilter'
|
|
);
|
|
const newFilterName = filterDatum.name || filterDatum.value.toString();
|
|
|
|
setProperties(this, {
|
|
currentCategoryFilter: filterType === 'category' && newFilterName !== currentCategoryFilter ? newFilterName : '',
|
|
currentSeverityFilter: filterType === 'severity' && newFilterName !== currentSeverityFilter ? newFilterName : ''
|
|
});
|
|
}
|
|
}
|