import moment from 'moment/moment';
import Chart from 'chart.js';
$(() => {
const usageFormSelector = '.usage_index';
const apiToken = $(usageFormSelector).find('input[name="api_token"]').val();
// Builds an object whose keys are the topic fro the select options and value its the value
// associated to the attribute data-url of each option
const topicToURL = $(`${usageFormSelector} select[name="topic"]`).find('option').map((i, el) => {
const topic = $(el);
return { [topic.val()]: topic.attr('data-url') };
}).get() // An array of objects { topic: URL }
.reduce((acc, value) => $.extend(acc, value), {});
const rangeDatesUpToLastYearFromNow = () => {
const getLastMonth = () => moment().subtract(1, 'month').clone();
const rangeDates = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1].reduce((acc, v, i) => {
const id = getLastMonth().subtract(i, 'month').format('MMM-YY');
acc[id] = {
start_date: getLastMonth().startOf('month').subtract(i, 'month').format('YYYY-MM-DD'),
end_date: getLastMonth().endOf('month').subtract(i, 'month').format('YYYY-MM-DD'),
id,
};
return acc;
}, {});
return rangeDates;
};
const createChart = ({ selector, data, appendTolabel = '' } = {}) => {
new Chart($(selector), { // eslint-disable-line no-new
type: 'bar',
data: {
labels: Object.keys(data),
datasets: [{
data: Object.keys(data).map(k => data[k]),
backgroundColor: '#4F5253',
// TODO parameterised according to roadmap main colour instance
}],
},
options: {
legend: {
display: false,
},
tooltips: {
callbacks: {
label: tooltipItem => `${tooltipItem.yLabel} ${appendTolabel}`,
},
},
scales: {
yAxes: [{
ticks: { min: 0, suggestedMax: 50 },
}],
},
},
});
};
/*
Submit event associated to the filter by dates form
*/
$(usageFormSelector).on('submit', (e) => {
e.preventDefault();
const target = $(e.target);
const topic = target.find('select[name="topic"]').val();
const orgId = target.find('select[name="org_id"]').val() || target.find('input[name="org_id"]').val();
$('[data-topics]').hide(); // Hides data-topics container
$('[data-topic]').hide(); // Hides any data-topic specific
const ajaxSettings = ({ totals = false } = {}) => ({
headers: { Authorization: `Token token="${apiToken}"` },
url: topicToURL[topic],
data: totals ? { topic, org_id: orgId } : target.serialize(),
});
// Awaits until both AJAX request responds.
// Note, the success handler is only executed if both AJAX requests return success
$.when($.ajax(ajaxSettings()), $.ajax(ajaxSettings({ totals: true }))).then(
(dataRangeSuccessCb, dataTotalsSuccessCb) => {
let dataRange = null;
let dataTotals = null;
if (dataRangeSuccessCb[0]) { // data is the first argument of the successCb ranges
const dataKeys = Object.keys(dataRangeSuccessCb[0]);
// We assume the dataRange is the first key of the object responded
dataRange = dataKeys.length > 0 ? dataRangeSuccessCb[0][dataKeys[0]] : null;
}
if (dataTotalsSuccessCb[0]) { // data is the first argument of the successCb for totals
const dataKeys = Object.keys(dataTotalsSuccessCb[0]);
// We assume the dataTotals is the first key of the object responded
dataTotals = dataKeys.length > 0 ? dataTotalsSuccessCb[0][dataKeys[0]] : null;
}
const dataTopics = $('[data-topics]');
const views = $(`[data-topic="${topic}"]`);
dataRange !== null ? dataTopics.find('[data-range]').html(dataRange) : undefined; // eslint-disable-line no-unused-expressions
dataTotals !== null ? dataTopics.find('[data-totals]').html(dataTotals) : undefined; // eslint-disable-line no-unused-expressions
views.show();
dataTopics.show();
},
); // TODO request error handling
});
/*
Click event associated to each Export button
*/
$('button.stat[data-url]').on('click', (e) => {
const rangeDates = rangeDatesUpToLastYearFromNow();
$.ajax({
headers: { Authorization: `Token token="${apiToken}"` },
url: $(e.currentTarget).attr('data-url'),
data: { range_dates: rangeDates },
}).then((data, statusText, jqXHR) => {
/* eslint-env browser */
const blob = new Blob([data], { type: 'text/csv' });
// Attemps to match the filename from the Content-Disposition header produced by the API
const match = /filename="([^"]*)"/.exec(jqXHR.getResponseHeader('Content-Disposition'));
const link = $('<a />', {
href: URL.createObjectURL(blob),
download: match ? match[1] : 'export.csv',
});
$('body').append(link);
link[0].click();
link.remove();
});
});
const yearlySuccesHandler = ({ data, selector } = {}) => {
const keys = Object.keys(data); // Keys are Month-Year strings and values might be [0...N]
if (keys.find(k => data[k] > 0)) {
createChart({ selector, data });
} else {
$(selector).prev().show();
}
};
// Sends an AJAX request to our two current endpoints that generate yearly data
// (e.g. users_joined_api_v0_statistics_path, created_plans_api_v0_statistics_path )
// and draws a barChart when success response is found
const initialise = () => {
// Only fire AJAX requests if topicToURL object has keys, i.e. topics mapping to URLs
if (Object.keys(topicToURL).length > 0) {
const rangeDates = rangeDatesUpToLastYearFromNow();
$.ajax({
headers: { Authorization: `Token token="${apiToken}"` },
url: topicToURL.users,
data: { range_dates: rangeDates },
}).then((data) => {
yearlySuccesHandler({ data, selector: '#yearly_users' });
}); // TODO request error handling
$.ajax({
headers: { Authorization: `Token token="${apiToken}"` },
url: topicToURL.plans,
data: { range_dates: rangeDates },
}).then((data) => {
yearlySuccesHandler({ data, selector: '#yearly_plans' });
}); // TODO request error handling
}
};
initialise();
});
$(() => {
const jQuerySelectorSelect = $('select[name=monthly_plans_by_template]');
let drawnChart = null;
const randomRgb = () => {
const { round, random } = Math;
const max = 255;
const f = () => round(random() * max);
return `rgb(${f()},${f()},${f()})`;
};
const yAxisLabel = date => moment(date).format('MMM-YY');
const drawHorizontalBar = (canvasSelector, data, aspectRatio = 1) => {
const chart = new Chart(canvasSelector, { // eslint-disable-line no-new
type: 'horizontalBar',
data,
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio,
scales: {
xAxes: [{
position: 'top',
ticks: { beginAtZero: true, stepSize: 10 },
}],
},
},
});
return chart;
};
const buildData = (data) => {
const labels = data.map(current => yAxisLabel(current.date));
const datasetsMap = data.reduce((acc, statCreatedPlan) => {
statCreatedPlan.by_template.forEach((template) => {
if (!acc[template.name]) {
acc[template.name] = { label: template.name, data: [], backgroundColor: randomRgb() };
}
acc[template.name].data.push({ x: template.count, y: yAxisLabel(statCreatedPlan.date) });
});
return acc;
}, {});
const datasets = Object.keys(datasetsMap).map(key => datasetsMap[key]);
return { labels, datasets };
};
const fetch = (lastDayOfMonth, aspectRatio = 1) => {
const baseUrl = $('select[name="monthly_plans_by_template"]').attr('data-url');
$.ajax({
url: `${baseUrl}?start_date=${lastDayOfMonth}`,
}).then((data) => {
const chartData = buildData(data);
const canvasSelector = '#monthly_plans_by_template_canvas';
if (drawnChart) {
drawnChart.destroy();
}
drawnChart = drawHorizontalBar($(canvasSelector), chartData, aspectRatio);
});
};
// Set Aspect Rate (width of X-axis/height of Y-axis) based on
// choice of selectedLastDayOfMonth in Time picker string value. Note aspect
const getAspectRatio = (selectedLastDayOfMonth) => {
let aspectRatio;
try {
const now = new Date();
const dateOfSelectedMonth = new Date(selectedLastDayOfMonth);
const diff = new Date(now.getTime() - dateOfSelectedMonth.getTime());
const diffInMonths = diff.getUTCMonth();
switch (diffInMonths) {
case 0:
case 1:
case 2:
case 3:
aspectRatio = 4;
break;
case 4:
case 5:
aspectRatio = 3;
break;
case 7:
case 8:
case 9:
aspectRatio = 2;
break;
default:
aspectRatio = 1;
}
} catch (e) {
aspectRatio = 1;
}
return aspectRatio;
};
const handler = () => {
const selectedMonth = jQuerySelectorSelect.val();
if (selectedMonth) {
const aspectRatio = getAspectRatio(selectedMonth);
fetch(selectedMonth, aspectRatio);
}
};
jQuerySelectorSelect.on('change', (e) => {
e.preventDefault();
handler();
});
handler();
});