implement the list view for metrics

This commit is contained in:
jbai 2016-06-29 11:27:09 -07:00 committed by Mars Lan
parent 91c5ac8f1d
commit 6b9958b7c3
7 changed files with 410 additions and 26 deletions

View File

@ -257,6 +257,41 @@ public class Metric extends Controller
result.put("message", message);
return badRequest(result);
}
}
public static Result getMetricListViewByMetricId(Integer id)
{
ObjectNode result = Json.newObject();
result.put("status", "ok");
result.set("nodes", MetricsDAO.getMetricListViewMetricNodesByMetricId(id));
return ok(result);
}
public static Result getMetricListViewDashboards()
{
ObjectNode result = Json.newObject();
result.put("status", "ok");
result.set("nodes", MetricsDAO.getMetricListViewDashboardNodes());
return ok(result);
}
public static Result getMetricListViewGroups(String dashboard)
{
ObjectNode result = Json.newObject();
result.put("status", "ok");
result.set("nodes", MetricsDAO.getMetricListViewGroupsNodes(dashboard));
return ok(result);
}
public static Result getMetricListViewNodes(String dashboard, String group)
{
ObjectNode result = Json.newObject();
result.put("status", "ok");
result.set("nodes", MetricsDAO.getMetricListViewMetricNodes(dashboard, group));
return ok(result);
}
}

View File

@ -21,6 +21,7 @@ import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.primitives.Ints;
import models.MetricListViewNode;
import models.TreeNode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.EmptyResultDataAccessException;
@ -80,6 +81,9 @@ public class MetricsDAO extends AbstractMySQLOpenSourceDAO
"LEFT JOIN watch w ON (m.metric_id = w.item_id AND w.item_type = 'metric' AND w.user_id = ?) " +
"WHERE m.metric_id = ?";
private final static String GET_METRIC_BASIC_INFO_BY_ID = "SELECT metric_name, " +
"dashboard_name, metric_group, metric_category FROM dict_business_metric WHERE metric_id = ?";
private final static String GET_WATCHED_METRIC_ID = "SELECT id FROM watch " +
"WHERE user_id = ? and item_id = ? and item_type = 'metric'";
@ -103,23 +107,149 @@ public class MetricsDAO extends AbstractMySQLOpenSourceDAO
private final static String GET_METRIC_TREE_NODES = "SELECT DISTINCT metric_category, " +
"COALESCE(metric_name, '(Other)') as metric_name, metric_id " +
"FROM dict_business_metric WHERE dashboard_name = ? and metric_group = ? order by 1";
"FROM dict_business_metric WHERE dashboard_name = ? and metric_group = ? order by 2";
private final static String GET_METRIC_TREE_NODES_NO_DASHBOARD = "SELECT DISTINCT metric_category, " +
"COALESCE(metric_name, '(Other)') as metric_name, metric_id " +
"FROM dict_business_metric WHERE dashboard_name is null and metric_group = ? order by 1";
"FROM dict_business_metric WHERE dashboard_name is null and metric_group = ? order by 2";
private final static String GET_METRIC_TREE_NODES_NO_GROUP = "SELECT DISTINCT metric_category, " +
"COALESCE(metric_name, '(Other)') as metric_name, metric_id " +
"FROM dict_business_metric WHERE dashboard_name = ? and metric_group is null order by 1";
"FROM dict_business_metric WHERE dashboard_name = ? and metric_group is null order by 2";
private final static String GET_METRIC_TREE_NODES_NO_DASHBOARD_AND_GROUP = "SELECT DISTINCT metric_category, " +
"COALESCE(metric_name, '(Other)') as metric_name, metric_id " +
"FROM dict_business_metric WHERE dashboard_name is null and metric_group is null order by 1";
"FROM dict_business_metric WHERE dashboard_name is null and metric_group is null order by 2";
public final static String GET_METRIC_AUTO_COMPLETE_LIST = "SELECT DISTINCT metric_name " +
"FROM dict_business_metric WHERE metric_name is not null and metric_name != '' ORDER by 1";
public static JsonNode getMetricListViewDashboardNodes()
{
List<String> dashboardList = getJdbcTemplate().queryForList(GET_METRIC_TREE_DASHBOARD_NODES, String.class);
List<MetricListViewNode> nodes = new ArrayList<MetricListViewNode>();
if (dashboardList != null && dashboardList.size() > 0)
{
for (String dashboard : dashboardList)
{
MetricListViewNode node = new MetricListViewNode();
node.dashboard = dashboard;
node.nodeName = dashboard;
node.nodeUrl = "#/metrics/name/" + node.dashboard + "/page/1";
nodes.add(node);
}
}
return Json.toJson(nodes);
}
public static JsonNode getMetricListViewGroupsNodes(String dashboard)
{
List<MetricListViewNode> nodes = new ArrayList<MetricListViewNode>();
if (StringUtils.isBlank(dashboard))
{
return Json.toJson(nodes);
}
List<String> groupList = null;
if (dashboard.equalsIgnoreCase("(Other)"))
{
groupList = getJdbcTemplate().queryForList(GET_METRIC_TREE_OTHER_GROUP_NODES, String.class);
}
else
{
groupList = getJdbcTemplate().queryForList(GET_METRIC_TREE_GROUP_NODES, String.class, dashboard);
}
if (groupList != null && groupList.size() > 0)
{
for (String group : groupList)
{
MetricListViewNode node = new MetricListViewNode();
node.dashboard = dashboard;
node.group = group;
node.nodeName = group;
node.nodeUrl = "#/metrics/name/" + dashboard + "/" + group + "/page/1";
nodes.add(node);
}
}
return Json.toJson(nodes);
}
public static JsonNode getMetricListViewMetricNodes(String dashboard, String group)
{
List<MetricListViewNode> nodes = new ArrayList<MetricListViewNode>();
if (StringUtils.isBlank(dashboard) || StringUtils.isBlank(group))
{
return Json.toJson(nodes);
}
List<Map<String, Object>> rows = null;
if (dashboard.equalsIgnoreCase("(Other)"))
{
if (group.equalsIgnoreCase("(Other)"))
{
rows = getJdbcTemplate().queryForList(GET_METRIC_TREE_NODES_NO_DASHBOARD_AND_GROUP);
}
else
{
rows = getJdbcTemplate().queryForList(GET_METRIC_TREE_NODES_NO_DASHBOARD, group);
}
}
else
{
if (group.equalsIgnoreCase("(Other)"))
{
rows = getJdbcTemplate().queryForList(GET_METRIC_TREE_NODES_NO_GROUP, dashboard);
}
else
{
rows = getJdbcTemplate().queryForList(GET_METRIC_TREE_NODES, dashboard, group);
}
}
if (rows != null)
{
for (Map row : rows)
{
String category = (String)row.get(MetricRowMapper.METRIC_CATEGORY_COLUMN);
String name = (String)row.get(MetricRowMapper.METRIC_NAME_COLUMN);
String title = "";
if (StringUtils.isBlank(category))
{
title = name;
}
else
{
title = "{" + category + "} " + name;
}
MetricListViewNode node = new MetricListViewNode();
node.dashboard = dashboard;
node.group = group;
node.metric = name;
node.metricId = new Long((Integer)row.get(MetricRowMapper.METRIC_ID_COLUMN));
node.nodeName = title;
node.nodeUrl = "#/metrics/" + Long.toString(node.metricId);
nodes.add(node);
}
}
return Json.toJson(nodes);
}
public static JsonNode getMetricListViewMetricNodesByMetricId(Integer metricId)
{
List<MetricListViewNode> nodes = new ArrayList<MetricListViewNode>();
Metric metric = getMetricBasicInfoById(metricId);
if (StringUtils.isBlank(metric.dashboardName))
{
metric.dashboardName = "(Other)";
}
if (StringUtils.isBlank(metric.group))
{
metric.group = "(Other)";
}
return getMetricListViewMetricNodes(metric.dashboardName, metric.group);
}
public static JsonNode getMetricDashboardNodes()
{
List<String> dashboardList = getJdbcTemplate().queryForList(GET_METRIC_TREE_DASHBOARD_NODES, String.class);
@ -336,7 +466,37 @@ public class MetricsDAO extends AbstractMySQLOpenSourceDAO
return result;
}
public static Metric getMetricByID(int id, String user)
private static Metric getMetricBasicInfoById(int id)
{
List<Map<String, Object>> rows = null;
Metric metric = new Metric();
try
{
rows = getJdbcTemplate().queryForList(
GET_METRIC_BASIC_INFO_BY_ID,
id);
if (rows != null)
{
for (Map row : rows)
{
String dashboard = (String)row.get(MetricRowMapper.METRIC_DASHBOARD_NAME_COLUMN);
String group = (String)row.get(MetricRowMapper.METRIC_GROUP_COLUMN);
metric.dashboardName = dashboard;
metric.group = group;
}
}
}
catch(EmptyResultDataAccessException e)
{
Logger.error("Metric getMetricByID failed, id = " + id);
Logger.error("Exception = " + e.getMessage());
}
return metric;
}
public static Metric getMetricByID(int id, String user)
{
Integer userId = UserDAO.getUserIDByUserName(user);
Metric metric = null;

View File

@ -0,0 +1,24 @@
/**
* Copyright 2015 LinkedIn Corp. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
package models;
public class MetricListViewNode {
public String dashboard;
public String nodeName;
public String nodeUrl;
public String group;
public String metric;
public Long metricId;
}

View File

@ -26,8 +26,8 @@
</div>
@if(isInternal) {
<div id="treetab" class="tab-pane">
<div id="tree1" data-source="ajax" class="sampletree">
</div>
<div id="metriclist" class="list-group"></div>
<div id="tree1" data-source="ajax" class="sampletree" style="display: none;"></div>
</div>
}
<div id="flowtab" class="tab-pane">
@ -884,6 +884,21 @@
<div class="row">
<div class="col-xs-12">
{{#unless detailview}}
<div class="well well-sm">
<div class="row">
<div class="col-xs-11">
<ul class="breadcrumbs">
{{#each breadcrumbs as |crumb|}}
<li>
<a title="{{crumb.title}}" href="#/metrics/{{crumb.urn}}">
{{crumb.title}}
</a>
</li>
{{/each}}
</ul>
</div>
</div>
</div>
<div class="search-pagination">
<ul class="pager">
{{#unless first}}
@ -931,21 +946,6 @@
</div>
{{/unless}}
{{#unless detailview}}
<div class="well well-sm">
<div class="row">
<div class="col-xs-11">
<ul class="breadcrumbs">
{{#each breadcrumbs as |crumb|}}
<li>
<a title="{{crumb.title}}" href="#/metrics/{{crumb.urn}}">
{{crumb.title}}
</a>
</li>
{{/each}}
</ul>
</div>
</div>
</div>
<table class="table table-bordered search-results">
<tbody>
@ -996,8 +996,22 @@
</div>
</script>
<script type="text/x-handlebars" id="metric">
<div class="well well-sm">
<div class="row">
<div class="col-xs-11">
<ul class="breadcrumbs">
{{#each breadcrumbs as |crumb|}}
<li>
<a title="{{crumb.title}}" href="#/metrics/{{crumb.urn}}">
{{crumb.title}}
</a>
</li>
{{/each}}
</ul>
</div>
</div>
</div>
{{#metric-detail showLineage=showLineage model=model}}
{{/metric-detail}}
</script>

View File

@ -59,11 +59,19 @@ GET /api/v2/autocomplete/searchFlow controllers.api.v1.Search.getSea
GET /api/v1/list/datasets controllers.api.v1.Dataset.getDatasetListNodes()
GET /api/v1/list/metrics controllers.api.v1.Metric.getMetricListViewDashboards()
GET /api/v1/list/metrics/:dashboard controllers.api.v1.Metric.getMetricListViewGroups(dashboard: String)
GET /api/v1/list/metrics/:dashboard/:group controllers.api.v1.Metric.getMetricListViewNodes(dashboard: String, group: String)
GET /api/v1/list/metric/:id controllers.api.v1.Metric.getMetricListViewByMetricId(id: Int)
GET /api/v1/list/flows controllers.api.v1.Flow.getFlowListViewClusters()
GET /api/v1/list/flows/:application controllers.api.v1.Flow.getFlowListViewProjects(application:String)
GET /api/v1/list/flows/:application/:project controllers.api.v1.Flow.getFlowListViewFlows(application:String, project:String)
GET /api/v1/list/flows/:application/:project controllers.api.v1.Flow.getFlowListViewFlows(application:String, project:String)
GET /api/v1/datasets controllers.api.v1.Dataset.getPagedDatasets()

View File

@ -72,9 +72,11 @@
$('#treeviewbtn').removeClass('btn-default');
$('#listviewbtn').addClass('btn-primary');
$('#treeviewbtn').addClass('btn-default');
$('#tree1').hide();
$('#tree2').hide();
$('#tree3').hide();
$('#datasetlist').show();
$('#metriclist').show();
$('#flowlist').show();
});
@ -86,7 +88,9 @@
$('#listviewbtn').addClass('btn-default');
$('#treeviewbtn').addClass('btn-primary');
$('#datasetlist').hide();
$('#metriclist').hide();
$('#flowlist').hide();
$('#tree1').show();
$('#tree2').show();
$('#tree3').show();
});
@ -307,6 +311,43 @@ function renderFlowListView(nodes, flowId)
}
}
function renderMetricListView(nodes, metricId)
{
var folderTemplate = '<a href="$URL" class="list-group-item"><i class="fa fa-folder" style="margin-right: 5px;"> $NODE_NAME</i></a>';
var metricTemplate = '<a href="$URL" class="list-group-item"><i class="fa fa-plus-square-o" style="margin-right: 5px;"> $NODE_NAME</i></a>';
var activeTemplate = '<a href="$URL" class="active list-group-item"><i class="fa fa-plus-square-o" style="margin-right: 5px;"> $NODE_NAME</i></a>';
var obj = $('#metriclist');
if (!obj)
return;
obj.empty();
var activeObj;
for(var i = 0; i < nodes.length; i++)
{
if (nodes[i].metricId && nodes[i].metricId > 0)
{
if (metricId == nodes[i].metricId)
{
activeObj = obj.append(activeTemplate.replace('$NODE_NAME', nodes[i].nodeName).replace("$URL", nodes[i].nodeUrl));
}
else
{
obj.append(metricTemplate.replace('$NODE_NAME', nodes[i].nodeName).replace("$URL", nodes[i].nodeUrl));
}
}
else
{
obj.append(folderTemplate.replace('$NODE_NAME', nodes[i].nodeName).replace("$URL", nodes[i].nodeUrl));
}
}
if (activeObj)
{
var scrollToActiveNode = function() {
$("#tabSplitter").scrollTo(activeObj, 800)
}
}
}
function filterListView(category, filter)
{
var obj = $('#flowlist');
@ -314,6 +355,10 @@ function filterListView(category, filter)
{
obj = $('#datasetlist');
}
else if (category == 'Metrics')
{
obj = $('#metriclist');
}
if (!obj || !obj.children() || obj.children().length == 0)
{

View File

@ -5,6 +5,13 @@ App.MetricsRoute = Ember.Route.extend({
},
actions: {
getMetrics: function() {
var listUrl = 'api/v1/list/metrics';
$.get(listUrl, function(data) {
if (data && data.status == "ok"){
renderMetricListView(data.nodes);
}
});
var url = 'api/v1/metrics?size=10&page=' + metricsController.get('model.data.page');
currentTab = 'Metrics';
updateActiveTab();
@ -20,6 +27,12 @@ App.MetricsRoute = Ember.Route.extend({
});
App.MetricspageRoute = Ember.Route.extend({
setupController: function(controller, param) {
var listUrl = 'api/v1/list/metrics';
$.get(listUrl, function(data) {
if (data && data.status == "ok"){
renderMetricListView(data.nodes);
}
});
var url = 'api/v1/metrics?size=10&page=' + param.page;
currentTab = 'Metrics';
updateActiveTab();
@ -47,6 +60,12 @@ App.MetricspageRoute = Ember.Route.extend({
},
actions: {
getMetrics: function() {
var listUrl = 'api/v1/list/metrics';
$.get(listUrl, function(data) {
if (data && data.status == "ok"){
renderMetricListView(data.nodes);
}
});
var url = 'api/v1/metrics?size=10&page=' + metricsController.get('model.data.page');
currentTab = 'Metrics';
updateActiveTab();
@ -178,9 +197,25 @@ App.MetricRoute = Ember.Route.extend({
{
name = params.name;
}
var breadcrumbs;
$.get(url, function(data) {
if (data && data.status == "ok"){
controller.set("model", data.metric);
var dashboard = data.metric.dashboardName;
if (!dashboard)
{
dashboard = '(Other)';
}
var group = data.metric.group;
if (!group)
{
group = '(Other)';
}
breadcrumbs = [{"title":"METRICS_ROOT", "urn":"page/1"},
{"title":dashboard, "urn":"name/" + dashboard + "/page/1"},
{"title":group, "urn":"name/" + dashboard + "/" + group + "/page/1"},
{"title":data.metric.name, "urn": params.id}];
controller.set('breadcrumbs', breadcrumbs);
setTimeout(initializeXEditable(id,
data.metric.description,
data.metric.dashboardName,
@ -194,6 +229,21 @@ App.MetricRoute = Ember.Route.extend({
else if (params && params.metric)
{
id = params.metric.id;
var dashboard = params.metric.dashboardName;
if (!dashboard)
{
dashboard = '(Other)';
}
var group = params.metric.group;
if (!group)
{
group = '(Other)';
}
breadcrumbs = [{"title":"METRICS_ROOT", "urn":"page/1"},
{"title":name, "urn":"name/" + dashboard + "/page/1"},
{"title":group, "urn":"name/" + dashboard + "/" + group + "/page/1"},
{"title":params.metric.name, "urn": params.id}];
controller.set('breadcrumbs', breadcrumbs);
if (params.metric.category)
{
name = '{' + params.metric.category + '} ' + params.metric.name;
@ -210,6 +260,15 @@ App.MetricRoute = Ember.Route.extend({
params.metric.displayFactor,
params.metric.displayFactorSym), 500);
}
var listUrl = 'api/v1/list/metric/' + id;
$.get(listUrl, function(data) {
if (data && data.status == "ok"){
renderMetricListView(data.nodes, id);
}
});
if (name)
{
findAndActiveMetricNode(name, id);
@ -218,6 +277,15 @@ App.MetricRoute = Ember.Route.extend({
},
actions: {
getMetrics: function() {
var id = this.get('controller.model.id');
var listUrl = 'api/v1/list/metrics/' + id;
$.get(listUrl, function(data) {
if (data && data.status == "ok"){
renderMetricListView(data.nodes, id);
}
});
var url = 'api/v1/metrics/' + this.get('controller.model.id')
var _this = this
currentTab = 'Metrics';
@ -242,8 +310,16 @@ App.MetricnamepageRoute = Ember.Route.extend({
&& transition.resolvedModels.metricname.name)
{
var name = transition.resolvedModels.metricname.name;
var listUrl = 'api/v1/list/metrics/' + name;
$.get(listUrl, function(data) {
if (data && data.status == "ok"){
renderMetricListView(data.nodes);
}
});
var url = 'api/v1/metrics/name/' + name + '?page=' + params.page;
var breadcrumbs = [{"title":name, "urn":"name/" + name + "/page/1"}];
var breadcrumbs = [{"title":"METRICS_ROOT", "urn":"page/1"},
{"title":name, "urn":"name/" + name + "/page/1"}];
$.get(url, function(data) {
if (data && data.status == "ok"){
metricsController.set('model', data);
@ -273,6 +349,13 @@ App.MetricnamepageRoute = Ember.Route.extend({
},
actions: {
getMetrics: function() {
var listUrl = 'api/v1/list/metrics/' + metricsController.get('dashboard');
$.get(listUrl, function(data) {
if (data && data.status == "ok"){
renderMetricListView(data.nodes);
}
});
var url = 'api/v1/metrics/name/' + metricsController.get('dashboard')
url += '?size=10&page=' + metricsController.get('model.data.page');
currentTab = 'Metrics';
@ -306,10 +389,18 @@ App.MetricnamesubpageRoute = Ember.Route.extend({
{
group = transition.resolvedModels.metricgroup.group;
url += '/' + group + '?page=' + params.page;
breadcrumbs = [{"title":name, "urn":"name/" + name + "/page/1"},
breadcrumbs = [{"title":"METRICS_ROOT", "urn":"page/1"},
{"title":name, "urn":"name/" + name + "/page/1"},
{"title":group, "urn":"name/" + name + "/" + group + "/page/1"}];
}
var listUrl = 'api/v1/list/metrics/' + name + '/' + group;
$.get(listUrl, function(data) {
if (data && data.status == "ok"){
renderMetricListView(data.nodes);
}
});
$.get(url, function(data) {
if (data && data.status == "ok"){
metricsController.set('breadcrumbs', breadcrumbs);
@ -338,6 +429,13 @@ App.MetricnamesubpageRoute = Ember.Route.extend({
},
actions: {
getMetrics: function() {
var listUrl = 'api/v1/list/metrics/' + metricsController.get('dashboard')
+ '/' + metricsController.get('group');
$.get(listUrl, function(data) {
if (data && data.status == "ok"){
renderMetricListView(data.nodes);
}
});
var url = 'api/v1/metrics/name/' + metricsController.get('dashboard')
url += '/' + metricsController.get('group') + '?size=10&page=' + metricsController.get('model.data.page');
currentTab = 'Metrics';