mirror of
https://github.com/datahub-project/datahub.git
synced 2025-10-29 09:52:40 +00:00
Merge pull request #1328 from cptran777/dataset-health-final
Dataset health final
This commit is contained in:
commit
9d496cfe15
@ -4,10 +4,10 @@ import { task } from 'ember-concurrency';
|
|||||||
import ComputedProperty from '@ember/object/computed';
|
import ComputedProperty from '@ember/object/computed';
|
||||||
import { IChartDatum } from 'wherehows-web/typings/app/visualization/charts';
|
import { IChartDatum } from 'wherehows-web/typings/app/visualization/charts';
|
||||||
import { IHealthScore, IDatasetHealth } from 'wherehows-web/typings/api/datasets/health';
|
import { IHealthScore, IDatasetHealth } from 'wherehows-web/typings/api/datasets/health';
|
||||||
import { healthCategories, healthSeverity, healthDetail } from 'wherehows-web/constants/data/temp-mock/health';
|
import { readDatasetHealthByUrn, getCategory } from 'wherehows-web/utils/api/datasets/health';
|
||||||
import { readDatasetHealthByUrn } from 'wherehows-web/utils/api/datasets/health';
|
|
||||||
import { Tabs } from 'wherehows-web/constants/datasets/shared';
|
import { Tabs } from 'wherehows-web/constants/datasets/shared';
|
||||||
import { equal } from '@ember-decorators/object/computed';
|
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
|
* Used for the dataset health tab, represents the fieldnames for the health score table
|
||||||
@ -145,21 +145,38 @@ export default class DatasetHealthContainer extends Component {
|
|||||||
* @type {Task<TaskInstance<Promise<any>>, (a?: any) => TaskInstance<TaskInstance<Promise<any>>>>}
|
* @type {Task<TaskInstance<Promise<any>>, (a?: any) => TaskInstance<TaskInstance<Promise<any>>>>}
|
||||||
*/
|
*/
|
||||||
getContainerDataTask = task(function*(this: DatasetHealthContainer): IterableIterator<Promise<IDatasetHealth>> {
|
getContainerDataTask = task(function*(this: DatasetHealthContainer): IterableIterator<Promise<IDatasetHealth>> {
|
||||||
const { health } = yield readDatasetHealthByUrn(get(this, 'urn'));
|
const health: IDatasetHealth = yield readDatasetHealthByUrn(get(this, 'urn'));
|
||||||
// Pretend like we're getting data from somehwere
|
|
||||||
const healthData = {
|
|
||||||
categories: healthCategories,
|
|
||||||
severity: healthSeverity,
|
|
||||||
detail: healthDetail
|
|
||||||
};
|
|
||||||
|
|
||||||
setProperties(this, {
|
const details = health.validations || [];
|
||||||
categoryMetrics: healthData.categories,
|
const total = details.length;
|
||||||
severityMetrics: healthData.severity,
|
const categories: IObject<number> = {};
|
||||||
tableData: healthData.detail
|
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 };
|
||||||
});
|
});
|
||||||
|
|
||||||
return health; // Do something with health information
|
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
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
import { task } from 'ember-concurrency';
|
||||||
|
import { IDatasetHealth } from 'wherehows-web/typings/api/datasets/health';
|
||||||
|
import { readDatasetHealthByUrn } from 'wherehows-web/utils/api/datasets/health';
|
||||||
|
import { get, set } from '@ember/object';
|
||||||
|
|
||||||
|
export default class HealthScoreGauge extends Component {
|
||||||
|
/**
|
||||||
|
* The urn identifier for the dataset
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
urn: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the tag to be used in the rendered html element for this component
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
tagName = '';
|
||||||
|
|
||||||
|
elementId = <any>undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The health score to be passed to the container
|
||||||
|
*/
|
||||||
|
healthScore: number = 0;
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
get(this, 'getHealthScoreTask').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>>>>}
|
||||||
|
*/
|
||||||
|
getHealthScoreTask = task(function*(this: HealthScoreGauge): IterableIterator<Promise<IDatasetHealth>> {
|
||||||
|
const health: IDatasetHealth = yield readDatasetHealthByUrn(get(this, 'urn'));
|
||||||
|
set(this, 'healthScore', health.score * 100 || 0);
|
||||||
|
}).restartable();
|
||||||
|
}
|
||||||
@ -119,24 +119,44 @@ export default class VisualizationChartsScoreGauge extends Component {
|
|||||||
*/
|
*/
|
||||||
chartData: Array<IHighChartsDataConfig>;
|
chartData: Array<IHighChartsDataConfig>;
|
||||||
|
|
||||||
constructor() {
|
/**
|
||||||
super(...arguments);
|
* Performs update functions for the charts upon initial and subsequent renders
|
||||||
|
*/
|
||||||
|
updateChart(): void {
|
||||||
const chartOptions = getBaseGaugeConfig();
|
const chartOptions = getBaseGaugeConfig();
|
||||||
const chartData = getBaseChartDataConfig('score');
|
const chartData = getBaseChartDataConfig('score');
|
||||||
const maxScore = typeof this.maxScore === 'number' ? this.maxScore : 100;
|
const score = this.score || 0;
|
||||||
const score = this.score || NaN;
|
|
||||||
// Adds our information to the highcharts formatted configurations so that they can be read in the chart
|
// Adds our information to the highcharts formatted configurations so that they can be read in the chart
|
||||||
chartOptions.yAxis.max = maxScore;
|
chartOptions.yAxis.max = get(this, 'maxScore');
|
||||||
chartData[0].data = [score];
|
chartData[0].data = [score];
|
||||||
|
|
||||||
setProperties(this, {
|
setProperties(this, {
|
||||||
score,
|
|
||||||
maxScore,
|
|
||||||
chartOptions,
|
chartOptions,
|
||||||
chartData,
|
chartData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows us to rerender the graph when the score gets updated. Useful for when API calls haven't been
|
||||||
|
* resolved yet at initial render
|
||||||
|
*/
|
||||||
|
didUpdateAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.updateChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
// Prevents "modify twice in single render" issue
|
||||||
|
this.score = typeof this.score === 'number' ? this.score : 0;
|
||||||
|
|
||||||
|
setProperties(this, {
|
||||||
|
maxScore: typeof this.maxScore === 'number' ? this.maxScore : 100,
|
||||||
title: this.title || '',
|
title: this.title || '',
|
||||||
scoreDisplay: this.scoreDisplay || ScoreDisplay.percentage
|
scoreDisplay: this.scoreDisplay || ScoreDisplay.percentage
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
{{#if getHealthScoreTask.isIdle}}
|
||||||
|
{{visualization/charts/score-gauge
|
||||||
|
class="dataset-health-score"
|
||||||
|
score=healthScore
|
||||||
|
title="Health Score:"}}
|
||||||
|
{{else}}
|
||||||
|
{{pendulum-ellipsis-animation}}
|
||||||
|
{{/if}}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
{{high-charts chartOptions=chartOptions content=chartData}}
|
{{high-charts chartOptions=(readonly chartOptions) content=(readonly chartData)}}
|
||||||
<div class="score-gauge__legend-wrapper">
|
<div class="score-gauge__legend-wrapper">
|
||||||
<h6 class="score-gauge__legend-title">
|
<h6 class="score-gauge__legend-title">
|
||||||
{{title}}
|
{{title}}
|
||||||
|
|||||||
@ -62,10 +62,7 @@
|
|||||||
class="dataset-owner-list"
|
class="dataset-owner-list"
|
||||||
shouldShowDatasetHealth=shouldShowDatasetHealth}}
|
shouldShowDatasetHealth=shouldShowDatasetHealth}}
|
||||||
|
|
||||||
{{visualization/charts/score-gauge
|
{{datasets/containers/health-score-gauge urn=encodedUrn}}
|
||||||
class="dataset-health-score"
|
|
||||||
score=83
|
|
||||||
title="Health Score:"}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,26 @@ import { IHealthScoreResponse, IDatasetHealth } from 'wherehows-web/typings/api/
|
|||||||
*/
|
*/
|
||||||
const datasetHealthUrlByUrn = (urn: string): string => `${datasetUrlByUrn(urn)}/health`;
|
const datasetHealthUrlByUrn = (urn: string): string => `${datasetUrlByUrn(urn)}/health`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant for formatting of the validator string
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
const validatorFormat = 'com.linkedin.metadata.validators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful for removing extraneous information outside of category
|
||||||
|
* @type {RegExp}
|
||||||
|
*/
|
||||||
|
const validationRegex = new RegExp(`${validatorFormat}.|Validator`, 'gi');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string in the format 'com.linkedin.metadata.validators.[Category]Validator', extract
|
||||||
|
* Category and return it.
|
||||||
|
* @param validator - Given validator string
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const getCategory = (validator: string) => validator.replace(validationRegex, '');
|
||||||
|
|
||||||
export const readDatasetHealthByUrn = async (urn: string): Promise<IDatasetHealth> => {
|
export const readDatasetHealthByUrn = async (urn: string): Promise<IDatasetHealth> => {
|
||||||
const defaultResponse = { score: 0, validations: [] };
|
const defaultResponse = { score: 0, validations: [] };
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
module('Integration | Component | datasets/containers/health-score-gauge', function(hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function(assert) {
|
||||||
|
await render(hbs`{{datasets/containers/health-score-gauge}}`);
|
||||||
|
|
||||||
|
assert.ok(this.element, 'Renders without errors');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { getCategory } from 'wherehows-web/utils/api/datasets/health';
|
||||||
|
|
||||||
|
module('Unit | Utility | api/datasets/health', function() {
|
||||||
|
test('extracting category from validator string works', function(assert) {
|
||||||
|
const testSTtring = 'com.linkedin.metadata.validators.OwnershipValidator';
|
||||||
|
|
||||||
|
assert.equal(getCategory(testSTtring), 'Ownership');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user