Skip to content

Commit 0c0db49

Browse files
committed
Add Dashboard page with dc.js graphs via django rest framework
1 parent 2424b8c commit 0c0db49

File tree

4 files changed

+408
-15
lines changed

4 files changed

+408
-15
lines changed

tickets/serializers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from rest_framework import serializers
2+
from .models import Ticket
3+
from django.contrib.auth.models import User
4+
5+
6+
class TicketSerializer(serializers.ModelSerializer):
7+
class Meta:
8+
model = Ticket
9+
fields = ('ticket_type', 'summary', 'description',
10+
'priority', 'assigned_to', 'status', 'upvotes', 'created_date')

tickets/static/js/dashboard.js

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// $(document).ready(function() {
2+
// $('#datatabletest').DataTable();
3+
// });
4+
5+
// Fetch data from django rest API
6+
fetch('http://localhost:8000/tickets/api/tickets')
7+
.then(response => {
8+
return response.json();
9+
})
10+
.then(data => {
11+
console.log(data);
12+
drawGraphs(data);
13+
});
14+
15+
function drawGraphs(data) {
16+
let ndx = crossfilter(data);
17+
drawTicketTypePieChart(ndx);
18+
drawPriorityPieChart(ndx);
19+
drawAssignedToRowChart(ndx);
20+
drawStatusPieChart(ndx);
21+
drawUpvotesRowChart(ndx);
22+
drawStatusByMonthBarChart(ndx);
23+
// drawDataTable(ndx);
24+
dc.renderAll();
25+
}
26+
27+
// Status by month
28+
function drawStatusByMonthBarChart(ndx) {
29+
var dateCreatedDim = ndx.dimension(d => d.created_date.substring(0, 10));
30+
var statusGroup = dateCreatedDim
31+
.group()
32+
.reduce(reduceAdd, reduceRemove, reduceInitial);
33+
34+
function reduceAdd(i, d) {
35+
i[d.status] = (i[d.status] || 0) + 1;
36+
return i;
37+
}
38+
function reduceRemove(i, d) {
39+
i[d.status] = (i[d.status] || 0) - 1;
40+
return i;
41+
}
42+
function reduceInitial() {
43+
return {};
44+
}
45+
46+
print_filter(statusGroup);
47+
48+
// Stacked bar chart status by month
49+
dc.barChart('#statusByMonthBarChart')
50+
.width(470)
51+
.height(350)
52+
.dimension(dateCreatedDim)
53+
.group(statusGroup, 'New', d => d.value['New'])
54+
.stack(statusGroup, 'In Progress', d => d.value['In Progress'])
55+
.stack(statusGroup, 'Resolved', d => d.value['Resolved'])
56+
.xAxisLabel('Date', 25)
57+
.yAxisLabel('Number of Tickets', 25)
58+
.useViewBoxResizing(true)
59+
.xUnits(dc.units.ordinal)
60+
.renderHorizontalGridLines(true)
61+
.ordinalColors(['grey', 'orange', 'green'])
62+
.gap(60)
63+
.renderTitle(true)
64+
.title(function(d) {
65+
return [
66+
d.key + '\n',
67+
'New: ' + (d.value['New'] || '0'),
68+
'In Progress: ' + (d.value['In Progress'] || '0'),
69+
'Resolved: ' + (d.value['Resolved'] || '0')
70+
].join('\n');
71+
})
72+
.margins({ top: 30, left: 60, right: 20, bottom: 70 })
73+
.x(d3.scaleOrdinal());
74+
}
75+
76+
// TicketType Pie Chart
77+
function drawTicketTypePieChart(ndx) {
78+
let ticketTypeDim = ndx.dimension(d => d.ticket_type);
79+
let ticketTypeGroup = ticketTypeDim.group();
80+
81+
// Pie chart
82+
dc.pieChart('#ticketTypePieChart')
83+
.radius(120)
84+
.minAngleForLabel(0.2)
85+
.dimension(ticketTypeDim)
86+
.group(ticketTypeGroup)
87+
.ordinalColors(['#0D324D', '#73EEDC'])
88+
.height(295)
89+
.width(500)
90+
.label(d => d.key + ': ' + d.value)
91+
// .cx(330)
92+
// .cy(150)
93+
// .legend(
94+
// dc
95+
// .legend()
96+
// .x(30)
97+
// .y(65)
98+
// .autoItemWidth(true)
99+
// .itemHeight(32)
100+
// .gap(12)
101+
// )
102+
.useViewBoxResizing(true);
103+
}
104+
105+
// Priority Pie Chart
106+
function drawPriorityPieChart(ndx) {
107+
let priorityDim = ndx.dimension(d => d.priority);
108+
let priorityGroup = priorityDim.group();
109+
110+
// Priority Pie chart
111+
dc.pieChart('#priorityPieChart')
112+
.radius(120)
113+
.minAngleForLabel(0.2)
114+
.dimension(priorityDim)
115+
.group(priorityGroup)
116+
.ordinalColors(['green', 'orange', 'red'])
117+
.height(295)
118+
.width(500)
119+
.label(d => d.key + ': ' + d.value)
120+
// .cx(330)
121+
// .cy(150)
122+
// .legend(
123+
// dc
124+
// .legend()
125+
// .x(30)
126+
// .y(65)
127+
// .autoItemWidth(true)
128+
// .itemHeight(32)
129+
// .gap(12)
130+
// )
131+
.useViewBoxResizing(true);
132+
}
133+
134+
// Status Pie Chart
135+
function drawStatusPieChart(ndx) {
136+
let statusDim = ndx.dimension(d => d.status);
137+
let statusGroup = statusDim.group();
138+
139+
// Pie chart
140+
dc.pieChart('#statusPieChart')
141+
.radius(120)
142+
.minAngleForLabel(0.2)
143+
.dimension(statusDim)
144+
.group(statusGroup)
145+
.ordinalColors(['grey', 'orange', 'green'])
146+
.height(295)
147+
.width(500)
148+
.label(d => d.key + ': ' + d.value)
149+
// .cx(330)
150+
// .cy(150)
151+
// .legend(
152+
// dc
153+
// .legend()
154+
// .x(30)
155+
// .y(65)
156+
// .autoItemWidth(true)
157+
// .itemHeight(32)
158+
// .gap(12)
159+
// )
160+
.useViewBoxResizing(true);
161+
}
162+
163+
// Assigned To Row Chart
164+
function drawAssignedToRowChart(ndx) {
165+
let assignedToDim = ndx.dimension(dc.pluck('assigned_to'));
166+
let assignedToGroup = assignedToDim.group().reduceCount();
167+
168+
dc.rowChart('#assignedToRowChart')
169+
.width(500)
170+
.height(330)
171+
.gap(2)
172+
.renderTitleLabel(true)
173+
.titleLabelOffsetX(413)
174+
.label(function() {
175+
return '';
176+
})
177+
.rowsCap(5)
178+
.useViewBoxResizing(true)
179+
.dimension(assignedToDim)
180+
.group(assignedToGroup);
181+
}
182+
183+
// Most Upvoted Tickets
184+
function drawUpvotesRowChart(ndx) {
185+
let summaryDim = ndx.dimension(dc.pluck('summary'));
186+
let upvotesGroup = summaryDim.group().reduceSum(d => +d.upvotes);
187+
188+
dc.rowChart('#upvotesRowChart')
189+
.width(500)
190+
.height(330)
191+
.gap(2)
192+
// .renderTitleLabel(true)
193+
// .titleLabelOffsetX(413)
194+
.label(d => d.key + ': ' + d.value)
195+
.rowsCap(5)
196+
.useViewBoxResizing(true)
197+
.dimension(summaryDim)
198+
.group(upvotesGroup);
199+
}
200+
201+
// Test datatable
202+
// function drawDataTable(ndx) {
203+
// let dimension = ndx.dimension(d => d.dim);
204+
// // let group1 = dimension.groupAll();
205+
// // let group1 = dimension.group();
206+
207+
// let table1 = dc
208+
// .dataTable('#datatabletest')
209+
// // .tableview('#datatabletest')
210+
// .dimension(dimension)
211+
// .height(200)
212+
// .width(200)
213+
// .size(Infinity)
214+
// .columns(['summary', 'ticket_type', 'priority', 'status', 'upvotes']);
215+
// }
216+
217+
// Reset all chart
218+
$('.reset').click(function() {
219+
dc.filterAll();
220+
dc.redrawAll();
221+
});
222+
223+
// Print filter
224+
// * testing only
225+
function print_filter(filter) {
226+
var f = eval(filter);
227+
if (typeof f.length != 'undefined') {
228+
} else {
229+
}
230+
if (typeof f.top != 'undefined') {
231+
f = f.top(Infinity);
232+
} else {
233+
}
234+
if (typeof f.dimension != 'undefined') {
235+
f = f
236+
.dimension(function(d) {
237+
return '';
238+
})
239+
.top(Infinity);
240+
} else {
241+
}
242+
console.log(
243+
filter +
244+
'(' +
245+
f.length +
246+
') = ' +
247+
JSON.stringify(f)
248+
.replace('[', '[\n\t')
249+
.replace(/}\,/g, '},\n\t')
250+
.replace(']', '\n]')
251+
);
252+
}

tickets/templates/dashboard.html

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{% extends "base.html" %}
2+
{% load static from staticfiles %}
3+
{% load bootstrap_tags %}
4+
{% block head %}
5+
<!-- Datatables -->
6+
<!-- <link rel="stylesheet" href="//cdn.datatables.net/1.10.20/css/jquery.dataTables.min.css"> -->
7+
<!-- dc.css -->
8+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.9/dc.min.css"
9+
integrity="sha256-uq/xxnkXyjSgp47vyRtSvBEwWuxTFbtxbKwOxkmWIJM=" crossorigin="anonymous" />
10+
<!-- dashboard.css -->
11+
<link rel="stylesheet" href="{% static 'css/dashboard.css' %}" />
12+
{% endblock %}
13+
{% block title %}TrackIt | Dashboard{% endblock %}
14+
{% block page_heading %}<i class="material-icons-outlined">assessment</i> Dashboard
15+
<button class="btn btn-primary float-right reset"><i class="material-icons align-bottom">restore</i> Reset</button>
16+
{% endblock %}
17+
{% block content %}
18+
<div class="row">
19+
<div class="col col-4">
20+
<h5>Features vs Bug</h5>
21+
<div id="ticketTypePieChart"></div>
22+
</div>
23+
<div class="col col-4">
24+
<h5>Priority</h5>
25+
<div id="priorityPieChart"></div>
26+
</div>
27+
<div class="col col-4">
28+
<h5>Status</h5>
29+
<div id="statusPieChart"></div>
30+
</div>
31+
</div>
32+
<hr>
33+
<div class="row">
34+
<div class="col col-4">
35+
<h5>Assignee (Top 5)</h5>
36+
<div id="assignedToRowChart"></div>
37+
</div>
38+
<div class="col col-4">
39+
<h5>Popular Tickets (Top 5)</h5>
40+
<div id="upvotesRowChart"></div>
41+
</div>
42+
<div class="col col-4">
43+
<h5>Status by Date</h5>
44+
<div id="statusByMonthBarChart"></div>
45+
</div>
46+
</div>
47+
<div class="row">
48+
<div class="col col-12">
49+
<!-- <h5>DataTable</h5>
50+
<div id="datatabletest"></div> -->
51+
</div>
52+
</div>
53+
{% endblock %}
54+
55+
{% block scripts %}
56+
<!-- d3.js -->
57+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.15.0/d3.min.js"
58+
integrity="sha256-m0QmIsBXcOMiETRmpT3qg2IQ/i0qazJA2miCHzOmS1Y=" crossorigin="anonymous"></script>
59+
<!-- crossfilter.js -->
60+
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js"
61+
integrity="sha256-T9tvV3x+/vCnCoFciKNZwbaJ46q9lh6iZjD0ZjD95lE=" crossorigin="anonymous"></script>
62+
<!-- dc.js -->
63+
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.9/dc.min.js"
64+
integrity="sha256-unWojco2TOb01l2JB43J27kr4UQIkHk+PWce5A2eq8Y=" crossorigin="anonymous"></script>
65+
<!-- dataTables.js -->
66+
<!-- <script src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script> -->
67+
<!-- dc-tableview-light.js-->
68+
<script src="{% static 'js/dc-tableview-light.min.js' %}"></script>
69+
<!-- main.js -->
70+
<script src="{% static 'js/dashboard.js' %}"></script>
71+
{% endblock %}

0 commit comments

Comments
 (0)