diff --git a/wherehows-web/app/components/datasets/containers/dataset-health.ts b/wherehows-web/app/components/datasets/containers/dataset-health.ts index fdcad18c6c..2f96c93fee 100644 --- a/wherehows-web/app/components/datasets/containers/dataset-health.ts +++ b/wherehows-web/app/components/datasets/containers/dataset-health.ts @@ -4,10 +4,10 @@ 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 { healthCategories, healthSeverity, healthDetail } from 'wherehows-web/constants/data/temp-mock/health'; -import { readDatasetHealthByUrn } from 'wherehows-web/utils/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 @@ -145,21 +145,38 @@ export default class DatasetHealthContainer extends Component { * @type {Task>, (a?: any) => TaskInstance>>>} */ getContainerDataTask = task(function*(this: DatasetHealthContainer): IterableIterator> { - const { health } = yield readDatasetHealthByUrn(get(this, 'urn')); - // Pretend like we're getting data from somehwere - const healthData = { - categories: healthCategories, - severity: healthSeverity, - detail: healthDetail - }; + const health: IDatasetHealth = yield readDatasetHealthByUrn(get(this, 'urn')); - setProperties(this, { - categoryMetrics: healthData.categories, - severityMetrics: healthData.severity, - tableData: healthData.detail + const details = health.validations || []; + const total = details.length; + const categories: IObject = {}; + const severities: IObject = {}; + + // Go through the details and find the COUNT of severity and category groupings + const tableData: Array = 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 = Object.keys(categories).map(category => ({ + name: category, + value: Math.round((categories[category] / total) * 100) + })); + + const severityMetrics: Array = Object.keys(severities).map(severity => ({ + name: severity, + value: Math.round((severities[severity] / total) * 100) + })); + + setProperties(this, { + categoryMetrics, + severityMetrics, + tableData + }); }); /** diff --git a/wherehows-web/app/components/datasets/containers/health-score-gauge.ts b/wherehows-web/app/components/datasets/containers/health-score-gauge.ts new file mode 100644 index 0000000000..08d7e5feb2 --- /dev/null +++ b/wherehows-web/app/components/datasets/containers/health-score-gauge.ts @@ -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 = 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>, (a?: any) => TaskInstance>>>} + */ + getHealthScoreTask = task(function*(this: HealthScoreGauge): IterableIterator> { + const health: IDatasetHealth = yield readDatasetHealthByUrn(get(this, 'urn')); + set(this, 'healthScore', health.score * 100 || 0); + }).restartable(); +} diff --git a/wherehows-web/app/components/visualization/charts/score-gauge.ts b/wherehows-web/app/components/visualization/charts/score-gauge.ts index 7be5c597ee..bacce33c38 100644 --- a/wherehows-web/app/components/visualization/charts/score-gauge.ts +++ b/wherehows-web/app/components/visualization/charts/score-gauge.ts @@ -119,24 +119,44 @@ export default class VisualizationChartsScoreGauge extends Component { */ chartData: Array; - constructor() { - super(...arguments); - + /** + * Performs update functions for the charts upon initial and subsequent renders + */ + updateChart(): void { const chartOptions = getBaseGaugeConfig(); const chartData = getBaseChartDataConfig('score'); - const maxScore = typeof this.maxScore === 'number' ? this.maxScore : 100; - const score = this.score || NaN; + const score = this.score || 0; + // 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]; setProperties(this, { - score, - maxScore, 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 || '', scoreDisplay: this.scoreDisplay || ScoreDisplay.percentage }); + + this.updateChart(); } } diff --git a/wherehows-web/app/templates/components/datasets/containers/health-score-gauge.hbs b/wherehows-web/app/templates/components/datasets/containers/health-score-gauge.hbs new file mode 100644 index 0000000000..4833ba635d --- /dev/null +++ b/wherehows-web/app/templates/components/datasets/containers/health-score-gauge.hbs @@ -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}} diff --git a/wherehows-web/app/templates/components/visualization/charts/score-gauge.hbs b/wherehows-web/app/templates/components/visualization/charts/score-gauge.hbs index 8ad1b508c0..91fc410dcd 100644 --- a/wherehows-web/app/templates/components/visualization/charts/score-gauge.hbs +++ b/wherehows-web/app/templates/components/visualization/charts/score-gauge.hbs @@ -1,4 +1,4 @@ -{{high-charts chartOptions=chartOptions content=chartData}} +{{high-charts chartOptions=(readonly chartOptions) content=(readonly chartData)}}
{{title}} diff --git a/wherehows-web/app/templates/datasets/dataset.hbs b/wherehows-web/app/templates/datasets/dataset.hbs index 44c2cf9ebe..49b6f24a3e 100644 --- a/wherehows-web/app/templates/datasets/dataset.hbs +++ b/wherehows-web/app/templates/datasets/dataset.hbs @@ -62,10 +62,7 @@ class="dataset-owner-list" shouldShowDatasetHealth=shouldShowDatasetHealth}} - {{visualization/charts/score-gauge - class="dataset-health-score" - score=83 - title="Health Score:"}} + {{datasets/containers/health-score-gauge urn=encodedUrn}} {{/if}}
diff --git a/wherehows-web/app/utils/api/datasets/health.ts b/wherehows-web/app/utils/api/datasets/health.ts index ffc16788dd..0ed272832e 100644 --- a/wherehows-web/app/utils/api/datasets/health.ts +++ b/wherehows-web/app/utils/api/datasets/health.ts @@ -9,6 +9,26 @@ import { IHealthScoreResponse, IDatasetHealth } from 'wherehows-web/typings/api/ */ 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 => { const defaultResponse = { score: 0, validations: [] }; diff --git a/wherehows-web/tests/integration/components/datasets/containers/health-score-gauge-test.js b/wherehows-web/tests/integration/components/datasets/containers/health-score-gauge-test.js new file mode 100644 index 0000000000..5e973a4c6b --- /dev/null +++ b/wherehows-web/tests/integration/components/datasets/containers/health-score-gauge-test.js @@ -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'); + }); +}); diff --git a/wherehows-web/tests/unit/utils/api/datasets/validator-test.js b/wherehows-web/tests/unit/utils/api/datasets/validator-test.js new file mode 100644 index 0000000000..aa0f58e362 --- /dev/null +++ b/wherehows-web/tests/unit/utils/api/datasets/validator-test.js @@ -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'); + }); +});