Skip to content
Snippets Groups Projects
Commit d925050d authored by Allen Gilliland's avatar Allen Gilliland
Browse files

Merge pull request #517 from metabase/eol_operator_pages

EOL old Operator custom explore pages.
parents 2b486d01 06b57b12
No related branches found
No related tags found
No related merge requests found
......@@ -22,8 +22,7 @@ var Corvus = angular.module('corvus', [
'corvus.dashboard',
'corvus.explore',
'corvus.home',
'corvus.operator', // this is a short term hack
'corvus.reserve',
'corvus.reserve', // this is a short term hack
'corvus.user',
'corvus.setup',
'corvusadmin.databases',
......
'use strict';
var OperatorControllers = angular.module('corvus.operator.controllers', []);
OperatorControllers.controller('SpecialistList', ['$scope', 'Metabase', 'Operator',
function($scope, Metabase, Operator) {
// set initial defaults for sorting
$scope.orderByField = "nick";
$scope.reverseSort = false;
Operator.queryInfo().then(function(queryInfo){
// TODO: we need offset support in dataset_query if we want to do paging
// TODO: ideally we can search by (name, id, store)
$scope.search = function () {
Metabase.dataset({
'database': queryInfo.database,
'type': 'query',
'query': {
'source_table': queryInfo.specialist_table,
'aggregation': ['rows'],
'breakout': [null],
'filter':[null, null],
'limit': null
}
}, function (queryResponse) {
// TODO: we should check that the query succeeded
$scope.specialists = Operator.convertToObjects(queryResponse.data);
}, function (error) {
console.log(error);
});
};
$scope.search();
//run saved SQL queries for overview metrics in right pane
Metabase.dataset({
'database': queryInfo.database,
'type': 'result',
'result':{
query_id: queryInfo.specialist_overview_avg_rating_query
}
}, function(queryResponse){
$scope.overviewAvgRating = queryResponse.data.rows[0][0];
}, function(error){
console.log("error:");
console.log(error);
});
Metabase.dataset({
'database': queryInfo.database,
'type': 'result',
'result': {
query_id: queryInfo.specialist_overview_avg_response_time_query
}
}, function(queryResponse){
$scope.overviewAvgResponseTimeSecs = queryResponse.data.rows[0][0];
}, function(error){
console.log("error:");
console.log(error);
});
}, function(reason){
console.log("failed to get queryInfo:");
console.log(reason);
});
}
]);
OperatorControllers.controller('SpecialistDetail', ['$scope', '$routeParams', 'Metabase', 'Operator',
function($scope, $routeParams, Metabase, Operator) {
// set the default ordering to the last message sent, as the field team is generally concerned with
// recent messages
$scope.orderByField = "time_newmessage_server";
// set reverse to true so we see the most recent messages first
$scope.reverseSort = true;
Operator.queryInfo().then(function(queryInfo){
if ($routeParams.specialistId) {
Metabase.dataset({
'database': queryInfo.database,
'type': 'query',
'query': {
'source_table': queryInfo.specialist_table,
'aggregation': ['rows'],
'breakout': [null],
'filter':['=', queryInfo.specialist_id_field, parseInt($routeParams.specialistId, 10)],
'limit': null
}
}, function (queryResponse) {
$scope.specialist = Operator.convertToObjects(queryResponse.data)[0];
// grab conversations
Metabase.dataset({
'database': queryInfo.database,
'type': 'query',
'query': {
'source_table': queryInfo.conversations_table,
'aggregation': ['rows'],
'breakout': [null],
'filter':['=', queryInfo.conversations_specialist_fk, parseInt($routeParams.specialistId, 10)],
'limit': null
}
}, function (response) {
$scope.conversations = Operator.convertToObjects(response.data);
}, function (error) {
console.log(error);
});
}, function (error) {
console.log(error);
});
}
}, function(reason){
console.log("failed to get queryInfo:");
console.log(reason);
});
}
]);
OperatorControllers.controller('ConversationDetail', ['$scope', '$routeParams', 'Metabase', 'Operator',
function($scope, $routeParams, Metabase, Operator) {
$scope.toObject = function(str) {
//var unescaped = str.replace(/\\"/g, '"');
return angular.fromJson(str);
};
Operator.queryInfo().then(function(queryInfo){
if ($routeParams.conversationId) {
Metabase.dataset({
'database': queryInfo.database,
'type': 'query',
'query': {
'source_table': queryInfo.conversations_table,
'aggregation': ['rows'],
'breakout': [null],
'filter':['=', queryInfo.conversations_id_field, $routeParams.conversationId],
'limit': null
}
}, function (queryResponse) {
$scope.conversation = Operator.convertToObjects(queryResponse.data)[0];
// grab messages
// TODO: ensure ordering by message timestamp
Metabase.dataset({
'database': queryInfo.database,
'type': 'query',
'query': {
'source_table': queryInfo.messages_table,
'aggregation': ['rows'],
'breakout': [null],
'filter':['=', queryInfo.messages_table_conversation_fk, $routeParams.conversationId],
'limit': null
}
}, function (response) {
$scope.messages = Operator.convertToObjects(response.data);
// sort them by timestamp
$scope.messages.sort(function compare (a, b) {
if (a.time_updated_server < b.time_updated_server)
return -1;
if (a.time_updated_server > b.time_updated_server)
return 1;
return 0;
});
}, function (error) {
console.log(error);
});
}, function (error) {
console.log(error);
});
}
}, function(reason){
console.log("failed to get queryInfo:");
console.log(reason);
});
}
]);
'use strict';
var Operator = angular.module('corvus.operator', [
'ngRoute',
'ngCookies',
'corvus.filters',
'corvus.directives',
'corvus.services',
'corvus.metabase.services',
'corvus.operator.controllers',
'corvus.operator.services'
]);
Operator.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/operator/specialist/:specialistId', {templateUrl: '/app/operator/partials/specialist_detail.html', controller: 'SpecialistDetail'});
$routeProvider.when('/operator/specialist/', {templateUrl: '/app/operator/partials/specialist_list.html', controller: 'SpecialistList'});
$routeProvider.when('/operator/conversation/:conversationId', {templateUrl: '/app/operator/partials/conversation_detail.html', controller: 'ConversationDetail'});
}]);
'use strict';
/*jslint browser:true */
/*global _*/
/* Services */
var OperatorServices = angular.module('corvus.operator.services', []);
OperatorServices.service('Operator', ['$resource', '$q', 'Metabase', 'Query',
function($resource, $q, Metabase, Query) {
var OPERATOR_DB_NAME = "operator";
var SPECIALIST_TABLE_NAME = "ps_specialist_details_with_calc_metrics";
var SPECIALIST_ID_FIELD_NAME = "specialist_id";
var CONVERSATIONS_TABLE_NAME = "ag_conversations";
var CONVERSATIONS_ID_FIELD_NAME = "channel_id";
var CONVERSATIONS_SPECIALIST_FK_NAME = "specialist_id";
var MESSAGES_TABLE_NAME = "chat_message";
var MESSAGES_CONVERSATIONS_FK_NAME = "channel_id";
var SPECIALIST_OVERVIEW_AVG_RATING_QUERY_NAME = "Specialist Entity Avg Rating";
var SPECIALIST_OVERVIEW_AVG_RESPONSE_TIME_QUERY = "Specialist Entity Avg Response Time Secs";
this.queryInfo = function() {
var deferred = $q.defer();
var queryInfo = {};
Metabase.db_list(function (dbs){
dbs.forEach(function(db){
if(db.name == OPERATOR_DB_NAME){
queryInfo.database = db.id;
Metabase.db_tables({dbId:db.id}, function(tables){
tables.forEach(function(table){
if(table.name == SPECIALIST_TABLE_NAME){
queryInfo.specialist_table = table.id;
}else if(table.name == CONVERSATIONS_TABLE_NAME){
queryInfo.conversations_table = table.id;
}else if(table.name == MESSAGES_TABLE_NAME){
queryInfo.messages_table = table.id;
}
});
Metabase.table_fields({tableId:queryInfo.specialist_table}, function(specialistTableFields){
specialistTableFields.forEach(function(field){
if(field.name == SPECIALIST_ID_FIELD_NAME){
queryInfo.specialist_id_field = field.id;
Metabase.table_fields({tableId:queryInfo.conversations_table}, function(conversationsTableFields){
conversationsTableFields.forEach(function(field){
if(field.name == CONVERSATIONS_ID_FIELD_NAME){
queryInfo.conversations_id_field = field.id;
}else if(field.name == CONVERSATIONS_SPECIALIST_FK_NAME){
queryInfo.conversations_specialist_fk = field.id;
}
});
Metabase.table_fields({tableId:queryInfo.messages_table}, function(messagesTableFields){
messagesTableFields.forEach(function(field){
if(field.name == MESSAGES_CONVERSATIONS_FK_NAME){
queryInfo.messages_table_conversation_fk = field.id;
Query.list({
filterMode: 'all'
}, function(queries){
queries.forEach(function(query){
if(query.name == SPECIALIST_OVERVIEW_AVG_RATING_QUERY_NAME){
queryInfo.specialist_overview_avg_rating_query = query.id;
}else if(query.name == SPECIALIST_OVERVIEW_AVG_RESPONSE_TIME_QUERY){
queryInfo.specialist_overview_avg_response_time_query = query.id;
}
});
deferred.resolve(queryInfo);
}, function(error){
console.log("error getting queries:");
console.log(error);
});
}
});
});
});
}
});
});
});
}
});
});
return deferred.promise;
};
this.convertToObjects = function (data) {
var rows = [];
for (var i = 0; i < data.rows.length; i++) {
var row = {};
for (var j = 0; j < data.cols.length; j++) {
var coldef = data.cols[j];
row[coldef.name] = data.rows[i][j];
}
rows.push(row);
}
return rows;
};
}
]);
<div class="col col-md-12">
<div class="row">
<div class="p4 border-bottom">
<h3>Conversation with
<a class="link" href="/operator/specialist/{{conversation.specialist_id}}">{{conversation.specialist_nick}}</a>
and {{conversation.sender_nick}}
</h3>
</div>
</div>
<div class="row">
<ul>
<li class="Message clearfix" ng-repeat="message in messages" ng-class="{'Message--alt' : message.sender_id == conversation.specialist_id}">
<div class="Message-sender inline-block text-bold">
<div ng-if="message.sender_id == conversation.specialist_id">{{conversation.specialist_nick}}</div>
<div ng-if="message.sender_id == conversation.sender_id">{{conversation.sender_nick}}</div>
</div>
<div class="Timestamp ml1 inline-block">{{message.time_updated_server | date : 'MMM d, y - hh:mm a'}}</div>
<p class="Message-text" ng-if="message.content_type == 'mixed'">{{toObject(message.content).text}}</p>
<p class="Message-text" ng-if="message.content_type != 'mixed'">{{message.content_type}}: {{message.content}}</p>
</li>
</ul>
</div>
</div>
<div class="col col-md-9">
<div class="row">
<div class="clearfix full-width">
<div class="p4 float-left">
<img class="EntityImage EntityImage--large" ng-src="{{specialist.avatar}}" ng-if="specialist.avatar">
<img src="" ng-if="!specialist.avatar" />
</div>
<div class="mt3 float-left">
<h2>
<a class="link" href="/operator/specialist/{{specialist.specialist_id}}">{{specialist.nick}}</a> <span ng-if="specialist.is_test_account">[test account]</span>
</h2>
<div class="inline-block">{{specialist.business_name}}</div>
<div class="inline-block ml2">Specialist since: {{specialist.ts_created | date : 'MMMM d, y'}}</div>
</div>
<div class="float-right m4">
<a class="Button mt1" href="mailto:team@operator.com?subject=Specialist%20{{specialist.nick}}">Share</a>
</div>
</div>
</div>
<div class="row">
<div class="mt3 px3">
<h4>Conversations</h4>
</div>
<div class="EntityTableWrapper">
<table class="EntityTable Table">
<thead>
<tr>
<th ng-click="orderByField='sender_nick'; reverseSort = !reverseSort" ng-class="'ColumnSelected' : orderByField == 'sender_nick'">With:
<span ng-if="orderByField == 'sender_nick'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th ng-click="orderByField='ts_start'; reverseSort = !reverseSort" ng-class="'ColumnSelected' : orderByField == 'ts_start'">Started:
<span ng-if="orderByField == 'ts_start'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th ng-click="orderByField='ts_newmsg'; reverseSort = !reverseSort" ng-class="'ColumnSelected' : orderByField == 'ts_newmsg'">Last message:
<span ng-if="orderByField == 'ts_newmsg'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th ng-click="orderByField='total_channel_msgs'; reverseSort = !reverseSort" ng-class="'ColumnSelected' : orderByField == 'total_channel_msgs'">Total messages:
<span ng-if="orderByField == 'total_channel_msgs'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th ng-click="orderByField='avg_response_time_secs'; reverseSort = !reverseSort" ng-class="'ColumnSelected' : orderByField == 'average_response_time_secs'">Avg Response Time (s)
<span ng-if="orderByField == 'average_response_time_secs'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th></th>
</tr>
</thead>
<tfoot>
</tfoot>
<tbody>
<tr ng-repeat="convo in conversations | orderBy:orderByField:reverseSort">
<td>
<span class="EntityName">{{convo.sender_nick}}</span>
</td>
<td>{{convo.ts_start | date : 'MMM d, y, hh:mm a'}}</td>
<td>{{convo.ts_newmsg | date : 'MMM d, y, hh:mm a'}}</td>
<td>{{convo.total_channel_msgs}}</td>
<td>{{convo.avg_response_time_secs}}</td>
<td><a class="link" href="/operator/conversation/{{convo.channel_id}}">View conversation</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col col-md-3">
<div class="row">
<div class="p3">
<h3 class="mt2">{{specialist.nick}}'s Metrics</h3>
</div>
<ol class="px2 py2">
<li class="Metric mb2 shadowed">
<div class="px3 py1">
<div class="Metric-value">{{specialist.avg_response_time_secs | readableTime}}</div>
<div class="Metric-title mb2">Average response time</div>
</div>
</li>
<li class="Metric mb2 shadowed">
<div class="px3 py1">
<div class="Metric-value">{{specialist.avg_rating}}</div>
<div class="Metric-title mb2">Average rating</div>
</div>
</li>
</ol>
</div>
</div>
<div class="col col-md-9">
<div class="row">
<div class="p3 border-bottom">
<div class="float-right">
<input class="input" type="text" ng-model="searchFilter" placeholder="Search specialists ...">
</div>
<h3 class="text-brand">Specialists</h2>
</div>
<div ng-if="!specialists">
<h3>Loading ...</h3>
</div>
<div class="EntityTableWrapper">
<table class="EntityTable Table">
<thead>
<tr>
<th ng-click="orderByField='nick'; reverseSort = !reverseSort" ng-class="{'EntityTable--columnSelected': orderByField == 'nick'}">Name
<span ng-if="orderByField == 'nick'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th ng-click="orderByField='business_name'; reverseSort = !reverseSort" ng-class="{'EntityTable--columnSelected': orderByField == 'business_name'}">Business
<span ng-if="orderByField == 'business_name'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th ng-click="orderByField='avg_response_time_secs'; reverseSort = !reverseSort" ng-class="{'EntityTable--columnSelected': orderByField == 'avg_response_time_secs'}">Avg response time (s)
<span ng-if="orderByField == 'avg_response_time_secs'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th ng-click="orderByField='avg_rating'; reverseSort = !reverseSort" ng-class="{'EntityTable--columnSelected': orderByField == 'avg_rating'}">Avg rating
<span ng-if="orderByField == 'avg_rating'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th ng-click="orderByField='ts_created'; reverseSort = !reverseSort" >Specialist since:
<span ng-if="orderByField == 'ts_created'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
<th ng-click="orderByField='most_recent_activity'; reverseSort = !reverseSort">Most recent activitity:
<span ng-if="orderByField == 'most_recent_activity'">
<span ng-show="!reverseSort">^</span>
<span ng-show="reverseSort">v</span>
</span>
</th>
</tr>
</thead>
<tfoot></tfoot>
<tbody>
<tr ng-repeat="specialist in specialists | orderBy:orderByField:reverseSort | filter:searchFilter">
<td class="clearfix">
<img class="EntityImage EntityImage--small float-left hide lg-show" src="{{specialist.avatar}}">
<a class="EntityName float-left ml1 link" href="/operator/specialist/{{specialist.specialist_id}}">{{specialist.nick}}</a>
</td>
<td>{{specialist.business_name}}</td>
<td>{{specialist.avg_response_time_secs}}</td>
<td>{{specialist.avg_rating}}</td>
<td>{{specialist.ts_created | date : 'MMMM d, y '}}</td>
<td>{{specialist.most_recent_activity}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col col-md-3">
<div class="row">
<div class="p3 border-bottom">
<h3>Specialist metrics</h3>
</div>
<ol class="px2 py2">
<li class="Metric mb2 shadowed">
<div class="px3 py1">
<div class="Metric-value">{{overviewAvgResponseTimeSecs | readableTime}}</div>
<div class="Metric-title mb2">Average response time</div>
</div>
</li>
<li class="Metric mb2 shadowed">
<div class="px3 py1">
<div class="Metric-value">{{overviewAvgRating}}</div>
<div class="Metric-title mb2">Average rating</div>
</div>
</li>
</ol>
</div>
</div>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment