Merge pull request #1287 from cptran777/metadata-health-header

Metadata health header
This commit is contained in:
Charlie Tran 2018-08-02 13:27:28 -07:00 committed by GitHub
commit b8d2f3f799
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 291 additions and 15 deletions

View File

@ -1,6 +1,10 @@
import Component from '@ember/component';
import { get } from '@ember/object';
import { get, computed, setProperties, getProperties } from '@ember/object';
import { task, TaskInstance } from 'ember-concurrency';
import ComputedProperty from '@ember/object/computed';
import { IChartDatum } from 'wherehows-web/typings/app/visualization/charts';
import healthCategories from 'wherehows-web/mirage/fixtures/health-categories';
import healthSeverity from 'wherehows-web/mirage/fixtures/health-severity';
/**
* This is the container component for the dataset health tab. It should contain the health bar graphs and a table
@ -14,6 +18,84 @@ export default class DatasetHealthContainer extends Component {
*/
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> = [];
/**
* 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();
}
@ -27,9 +109,36 @@ export default class DatasetHealthContainer extends Component {
* @type {Task<TaskInstance<Promise<any>>, (a?: any) => TaskInstance<TaskInstance<Promise<any>>>>}
*/
getContainerDataTask = task(function*(this: DatasetHealthContainer): IterableIterator<TaskInstance<Promise<any>>> {
// Do something in the future
// Pretend like we're getting data from somehwere
const healthData = {
categories: healthCategories,
severity: healthSeverity
};
setProperties(this, {
categoryMetrics: healthData.categories,
severityMetrics: healthData.severity
});
});
// Mock data for testing demo purposes, to be deleted once we have actual data and further development
testSeries = [{ name: 'Test1', value: 10 }, { name: 'Test2', value: 5 }, { name: 'Test3', value: 3 }];
/**
* 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;
setProperties(this, {
currentCategoryFilter: filterType === 'category' && newFilterName !== currentCategoryFilter ? newFilterName : '',
currentSeverityFilter: filterType === 'severity' && newFilterName !== currentSeverityFilter ? newFilterName : ''
});
}
}

View File

@ -0,0 +1,35 @@
import Component from '@ember/component';
import { IChartDatum } from 'wherehows-web/typings/app/visualization/charts';
import { setProperties } from '@ember/object';
import { noop } from 'wherehows-web/utils/helpers/functions';
export default class DatasetsHealthMetricsCharts extends Component {
/**
* Sets the classes for the rendered html element for the compoennt
* @type {Array<string>}
*/
classNames = ['dataset-health__metrics-charts'];
/**
* Pass through function meant to come from the dataset-health container that handles the selection of
* a filter, triggered when the user clicks on one of the bars of the bar chart.
* @param {IChartDatum} datum - the actual chart datum object so we know what was clicked
*/
onCategorySelect: (datum: IChartDatum) => void;
/**
* Pass through function meant to come from the dataset-health container that handles the selection of
* a filter, triggered when the user clicks on one of the bars of the bar chart.
* @param {IChartDatum} datum - the actual chart datum object so we know what was clicked
*/
onSeveritySelect: (datum: IChartDatum) => void;
constructor() {
super(...arguments);
setProperties(this, {
onCategorySelect: this.onCategorySelect || noop,
onSeveritySelect: this.onSeveritySelect || noop
});
}
}

View File

@ -2,6 +2,7 @@ import Component from '@ember/component';
import { IChartDatum } from 'wherehows-web/typings/app/visualization/charts';
import { computed, get, set, setProperties } from '@ember/object';
import ComputedProperty from '@ember/object/computed';
import { noop } from 'wherehows-web/utils/helpers/functions';
interface IBarSeriesDatum extends IChartDatum {
yOffset: number;
@ -34,7 +35,7 @@ export default class HorizontalBarChart extends Component {
* Sets the classes for the rendered html element for the component
* @type {Array<string>}
*/
classNames = ['vz-chart', 'viz-bar-chart', 'single-series'];
classNames = ['viz-chart', 'viz-bar-chart', 'single-series'];
/**
* Represents the series of data needed to power our chart. Format is
@ -155,13 +156,22 @@ export default class HorizontalBarChart extends Component {
};
}
/**
* Expected to be optionally passed in from the containing component, this function handles the action to
* be taken if a user selects an individual bar from the chart.
* @param {string} name - the "category" or "tag" that goes with each legend label
* @param {number} value - the value associated with each series datum
*/
onBarSelect: (name: string, value: number) => void;
constructor() {
super(...arguments);
// Applying passed in properties or setting to default values
setProperties(this, {
labelTagProperty: this.labelTagProperty || 'name',
labelAppendTag: this.labelAppendTag || '',
labelAppendValue: this.labelAppendValue || ''
labelAppendValue: this.labelAppendValue || '',
onBarSelect: this.onBarSelect || noop
});
}

View File

@ -26,6 +26,7 @@
@import 'dataset-fabric/all';
@import 'dataset-relationships/all';
@import 'visualization/all';
@import 'dataset-health/all';
@import 'nacho/nacho-button';
@import 'nacho/nacho-global-search';

View File

@ -0,0 +1 @@
@import 'metrics-charts';

View File

@ -0,0 +1,32 @@
.dataset-health {
&__metrics-charts {
@include nacho-container;
display: flex;
}
&__chart-container {
width: 100%;
box-sizing: border-box;
padding: 0 16px;
}
.severity-chart {
&__bar {
&--minor {
$green3: get-color(green3);
fill: $green3;
stroke: $green3;
}
&--warning {
$orange3: get-color(orange3);
fill: $orange3;
stroke: $orange3;
}
&--critical {
$red3: get-color(red3);
fill: $red3;
stroke: $red3;
}
}
}
}

View File

@ -22,4 +22,22 @@
font-size: 15px;
margin-bottom: 16px;
}
&__bar {
&:hover {
cursor: pointer;
transform: scale(1.02, 1);
height: 18px;
}
&--faded {
opacity: 0.5;
}
}
&__label {
&--faded {
opacity: 0.5;
}
}
}

View File

@ -1,7 +1,6 @@
Coming Soon!
<div style="width: 50%">
{{visualization/charts/horizontal-bar-chart
title="Test Chart"
series=testSeries
labelAppendValue="%"}}
</div>
<h3>Metadata Health</h3>
{{datasets/health/metrics-charts
categoryData=renderedCategories
severityData=renderedSeverity
onCategorySelect=(action onFilterSelect "category")
onSeveritySelect=(action onFilterSelect "severity")}}

View File

@ -0,0 +1,19 @@
<div class="dataset-health__chart-container">
{{#if categoryData}}
{{visualization/charts/horizontal-bar-chart
title="Categories"
series=categoryData
labelAppendValue="%"
onBarSelect=(action onCategorySelect)}}
{{/if}}
</div>
<div class="dataset-health__chart-container">
{{#if severityData}}
{{visualization/charts/horizontal-bar-chart
class="severity-chart"
title="Severity"
series=severityData
labelAppendValue="%"
onBarSelect=(action onSeveritySelect)}}
{{/if}}
</div>

View File

@ -3,12 +3,15 @@
<g class="highcharts-series-group">
<g class="highcharts-series highcharts-series-0 highcharts-bar-series highcharts-tracker highcharts-series-hover">
{{#each seriesData as |datum index|}}
<rect x="0" y="{{datum.yOffset}}" height="16" width="{{datum.barLength}}" class="highcharts-color-{{index}}" rx="2px" ry="2px"></rect>
<rect x="0" y="{{datum.yOffset}}" height="16" width="{{datum.barLength}}" rx="2px" ry="2px"
class="highcharts-color-{{index}} viz-bar-chart__bar {{if datum.isFaded "viz-bar-chart__bar--faded"}} {{datum.customColorClass}}"
{{action onBarSelect datum}}>
</rect>
{{/each}}
</g>
<g class="highcharts-data-labels highcharts-series-0 highcharts-bar-series highcharts-color-0 highcharts-tracker">
{{#each seriesData as |datum index|}}
<g class="highcharts-label highcharts-data-label">
<g class="highcharts-label highcharts-data-label viz-bar-chart__label {{if datum.isFaded "viz-bar-chart__label--faded"}}">
<text x="0" y="{{datum.labelOffset}}">
<tspan class="highcharts-emphasized">{{datum.value}}{{labelAppendValue}}</tspan>
<tspan> | {{get datum labelTagProperty}}</tspan>

View File

@ -4,4 +4,6 @@
export interface IChartDatum {
name: string;
value: number;
isFaded?: boolean;
customColorClass?: string;
}

View File

@ -0,0 +1 @@
export default [{ name: 'Compliance', value: 60 }, { name: 'Ownership', value: 40 }];

View File

@ -0,0 +1 @@
export default [{ name: 'Minor', value: 50 }, { name: 'Warning', value: 30 }, { name: 'Critical', value: 25 }];

View File

@ -0,0 +1,45 @@
import { moduleForComponent, test } from 'ember-qunit';
import { run } from '@ember/runloop';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('datasets/health/metrics-charts', 'Integration | Component | datasets/health/metrics-charts', {
integration: true
});
const chartClass = '.viz-bar-chart';
const labelClass = '.highcharts-label';
const labelValueClass = `${labelClass} .highcharts-emphasized`;
const barClass = `${chartClass}__bar`;
test('it renders', async function(assert) {
this.render(hbs`{{datasets/health/metrics-charts}}`);
assert.ok(this.$(), 'Renders without errors');
});
test('it provides the correct information', async function(assert) {
const categoryData = [{ name: 'Compliance', value: 60 }, { name: 'Ownership', value: 40 }];
const severityData = [
{ name: 'Minor', value: 50, customColorClass: 'severity-chart__bar--minor' },
{ name: 'Warning', value: 30, customColorClass: 'severity-chart__bar--warning' },
{ name: 'Critical', value: 25, customColorClass: 'severity-chart__bar--critical' }
];
this.setProperties({
categoryData,
severityData
});
this.render(hbs`{{datasets/health/metrics-charts
categoryData=categoryData
severityData=severityData}}`);
assert.ok(this.$(), 'Still renders without errors');
assert.equal(this.$(chartClass).length, 2, 'Renders 2 charts');
assert.equal(
this.$(`${labelValueClass}:eq(0)`)
.text()
.trim(),
'60%',
'Renders correct value for labels'
);
});