Skip to content

Commit

Permalink
Merge pull request rubyforgood#4784 from rubyforgood/4753-diplay-app-…
Browse files Browse the repository at this point in the history
…metric

Health Metric using Chart.js
  • Loading branch information
compwron authored Jun 5, 2023
2 parents 1dd5331 + 406cd0c commit 575fcf7
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 15 deletions.
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ GEM
unf_ext (0.0.8.2)
unicode-display_width (2.4.2)
uniform_notifier (1.16.0)
unparser (0.6.7)
diff-lcs (~> 1.3)
parser (>= 3.2.0)
view_component (3.0.0)
activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0)
Expand Down
12 changes: 12 additions & 0 deletions app/controllers/health_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class HealthController < ApplicationController
skip_before_action :authenticate_user!
skip_after_action :verify_authorized

def index
respond_to do |format|
Expand All @@ -10,4 +11,15 @@ def index
format.json { render json: {latest_deploy_time: Health.instance.latest_deploy_time} }
end
end

def case_contacts_creation_times_in_last_week
# Get the case contacts created in the last week
case_contacts = CaseContact.where("created_at >= ?", 10.week.ago)

# Extract the created_at timestamps and convert them to Unix time
timestamps = case_contacts.pluck(:created_at).map { |t| t.to_i }

# Return the timestamps as a JSON response
render json: {timestamps: timestamps}
end
end
1 change: 1 addition & 0 deletions app/javascript/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ require('./src/select')
require('./src/sidebar')
require('./src/tooltip')
require('./src/session_timeout_poller.js')
require('./src/display_app_metric.js')
120 changes: 120 additions & 0 deletions app/javascript/src/display_app_metric.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Chart, registerables } from 'chart.js'
import 'chartjs-adapter-luxon'

Chart.register(...registerables)

const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']

$(document).ready(function () {
$.ajax({
type: 'GET',
url: '/health/case_contacts_creation_times_in_last_week',
success: function (data) {
const timestamps = data.timestamps

const counts = getCountsByDayAndHour(timestamps)
const dataset = getDatasetFromCounts(counts)

createChart(dataset)
}
})
})

function getCountsByDayAndHour (timestamps) {
const counts = {}

for (let i = 0; i < timestamps.length; i++) {
const timestamp = new Date(timestamps[i] * 1000)
const day = days[timestamp.getUTCDay()]
const hour = timestamp.getUTCHours()
const key = day + ' ' + hour
counts[key] = (counts[key] || 0) + 1
}

return counts
}

function getDatasetFromCounts (counts) {
const dataset = []

for (const key in counts) {
const parts = key.split(' ')
const day = parts[0]
const hour = parseInt(parts[1])
const count = counts[key]

dataset.push({
x: hour,
y: days.indexOf(day),
r: Math.sqrt(count) * 2,
count
})
}

return dataset
}

function createChart (dataset) {
const ctx = document.getElementById('myChart').getContext('2d')
// eslint-disable-next-line no-unused-vars
const myChart = new Chart(ctx, {
type: 'bubble',
data: {
datasets: [{
label: 'Case Contacts Creation Times in Last Week',
data: dataset,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)'
}]
},
options: getChartOptions()
})
}

function getChartOptions () {
return {
scales: {
x: getXScale(),
y: getYScale()
},
plugins: {
tooltip: {
callbacks: {
label: getTooltipLabelCallback()
}
}
}
}
}

function getXScale () {
return {
ticks: {
beginAtZero: true,
stepSize: 1
}
}
}

function getYScale () {
return {
ticks: {
beginAtZero: true,
stepSize: 1,
callback: getYTickCallback()
}
}
}

function getYTickCallback () {
return function (value, index, values) {
return days[value]
}
}

function getTooltipLabelCallback () {
return function (context) {
const datum = context.dataset.data[context.dataIndex]
return datum.count + ' case contacts created on ' + days[datum.y] + ' at ' + datum.x + ':00'
}
}
2 changes: 2 additions & 0 deletions app/views/health/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
<div id="health">
<canvas id="myChart"></canvas>
<p class="text-center mt-5">Chart: Display App Metric</p>
</div>
6 changes: 5 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
root to: "all_casa_admins/sessions#new", as: :unauthenticated_all_casa_root
end

resources :health, only: %i[index]
resources :health, only: %i[index] do
collection do
get :case_contacts_creation_times_in_last_week
end
end

get "/.well-known/assetlinks.json", to: "android_app_associations#index"
resources :casa_cases, except: %i[destroy] do
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@
"bootstrap-datepicker": "^1.10.0",
"bootstrap-scss": "^5.2.3",
"bootstrap-select": "^1.13.18",
"chart.js": "^4.2.1",
"chartjs-adapter-luxon": "^1.3.1",
"datatables.net-dt": "^1.13.4",
"esbuild": "^0.17.19",
"faker": "^5.5.3",
"jquery": "^3.6.4",
"lodash": "^4.17.21",
"luxon": "^3.3.0",
"popper.js": "^1.16.1",
"sass": "^1.62.1",
"select2": "^4.0.13",
Expand Down
13 changes: 13 additions & 0 deletions spec/requests/health_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,17 @@
expect(hash_body.keys).to match_array(["latest_deploy_time"])
end
end

describe "GET #case_contacts_creation_times_in_last_week" do
it "returns timestamps of case contacts created in the last week" do
case_contact1 = create(:case_contact, created_at: 1.week.ago)
case_contact2 = create(:case_contact, created_at: 2.weeks.ago)
get case_contacts_creation_times_in_last_week_health_index_path
expect(response).to have_http_status(:ok)
expect(response.content_type).to include("application/json")
timestamps = JSON.parse(response.body)["timestamps"]
expect(timestamps).to include(case_contact1.created_at.to_i)
expect(timestamps).not_to include(case_contact2.created_at.iso8601(3))
end
end
end
31 changes: 17 additions & 14 deletions spec/system/imports/index_spec.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
require "rails_helper"

RSpec.describe "imports/index", type: :system do
let(:volunteer) { create(:volunteer) }
let(:admin) { create(:casa_admin) }

context "as a volunteer" do
before { sign_in volunteer }

it "redirects the user with an error message" do
volunteer = create(:volunteer)

sign_in volunteer
visit imports_path

expect(page).to have_selector(".alert", text: "Sorry, you are not authorized to perform this action.")
end
end

context "import volunteer csv with phone numbers", js: true do
let(:import_file_path) { Rails.root.join("spec", "fixtures", "volunteers.csv") }

it "shows sms opt in modal" do
import_file_path = Rails.root.join("spec", "fixtures", "volunteers.csv")
admin = create(:casa_admin)

sign_in admin
visit imports_path(:volunteer)

Expand All @@ -38,9 +37,10 @@
end

context "import volunteer csv without phone numbers", js: true do
let(:import_file_path) { Rails.root.join("spec", "fixtures", "volunteers_without_phone_numbers.csv") }

it "shows successful import" do
import_file_path = Rails.root.join("spec", "fixtures", "volunteers_without_phone_numbers.csv")
admin = create(:casa_admin)

sign_in admin
visit imports_path(:volunteer)

Expand All @@ -54,9 +54,10 @@
end

context "import supervisors csv with phone numbers", js: true do
let(:import_file_path) { Rails.root.join("spec", "fixtures", "supervisors.csv") }

it "shows sms opt in modal" do
import_file_path = Rails.root.join("spec", "fixtures", "supervisors.csv")
admin = create(:casa_admin)

sign_in admin
visit imports_path
click_on "supervisor-tab"
Expand All @@ -78,12 +79,14 @@
end

context "import supervisors csv without phone numbers", js: true do
let(:import_file_path) { Rails.root.join("spec", "fixtures", "supervisors_without_phone_numbers.csv") }

it "shows successful import" do
import_file_path = Rails.root.join("spec", "fixtures", "supervisors_without_phone_numbers.csv")
admin = create(:casa_admin)

sign_in admin
visit imports_path
click_link "supervisor-tab"

click_on "Import Supervisors"

expect(page).to have_content("Import Supervisors")

Expand Down
22 changes: 22 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,11 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"

"@kurkle/color@^0.3.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==

"@nodelib/[email protected]":
version "2.1.5"
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
Expand Down Expand Up @@ -2288,6 +2293,18 @@ char-regex@^1.0.2:
resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==

chart.js@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.2.1.tgz#d2bd5c98e9a0ae35408975b638f40513b067ba1d"
integrity sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==
dependencies:
"@kurkle/color" "^0.3.0"

chartjs-adapter-luxon@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.1.tgz#8ad8be7cc1521bacd6cd2ff4b013669d4dd49364"
integrity sha512-yxHov3X8y+reIibl1o+j18xzrcdddCLqsXhriV2+aQ4hCR66IYFchlRXUvrJVoxglJ380pgytU7YWtoqdIgqhg==

[email protected]:
version "2.24.0"
resolved "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz"
Expand Down Expand Up @@ -4283,6 +4300,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"

luxon@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.3.0.tgz#d73ab5b5d2b49a461c47cedbc7e73309b4805b48"
integrity sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==

make-dir@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz"
Expand Down

0 comments on commit 575fcf7

Please sign in to comment.