Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions integreat_cms/cms/templates/statistics/statistics_sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ <h3 class="heading font-bold text-black">
</h3>
</div>
<div class="flex flex-col gap-4 p-4 pt-2">
<label for="export-statistics">
{% translate "Choose statistics to export" %}
</label>
<select id="export-statistics">
<option value="" selected>
--- {% translate "please select" %} ---
</option>
<option value="total-accesses">
{% translate "Total Access Statistics" %}
</option>
<option value="page-accesses">
{% translate "Page Access Statistics" %}
</option>
</select>
<label for="export-format">
{% translate "Choose format" %}
</label>
Expand Down
16 changes: 14 additions & 2 deletions integreat_cms/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8983,13 +8983,25 @@ msgid "Export"
msgstr "Exportieren"

#: cms/templates/statistics/statistics_sidebar.html
msgid "Choose format"
msgstr "Format auswählen"
msgid "Choose statistics to export"
msgstr "Wähle Statistiken zum exportieren aus"

#: cms/templates/statistics/statistics_sidebar.html
msgid "please select"
msgstr "bitte auswählen"

#: cms/templates/statistics/statistics_sidebar.html
msgid "Total Access Statistics"
msgstr "Statistiken für Gesamtzugriffe"

#: cms/templates/statistics/statistics_sidebar.html
msgid "Page Access Statistics"
msgstr "Statistiken für Seitenzugriffe"

#: cms/templates/statistics/statistics_sidebar.html
msgid "Choose format"
msgstr "Format auswählen"

#: cms/templates/statistics/statistics_sidebar.html
msgid "Image/PNG"
msgstr "Bild/PNG"
Expand Down
116 changes: 61 additions & 55 deletions integreat_cms/static/src/js/analytics/statistics-charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,14 @@ const updateChart = async (): Promise<void> => {
* This function enables/disables the export button depending on whether an export format is selected or not.
*/
const toggleExportButton = () => {
// Only activate button if an export format is selected
// Only activate button if the statistics to export and an export format is selected
const exportFormat = document.getElementById("export-format") as HTMLSelectElement;
const exportStatistics = document.getElementById("export-statistics") as HTMLSelectElement;
const exportButton = document.getElementById("export-button") as HTMLInputElement;
if (!exportFormat || !exportButton) {
if (!exportFormat || !exportStatistics || !exportButton) {
return;
}
exportButton.disabled = exportFormat.value === "";
exportButton.disabled = exportFormat.value === "" || exportStatistics.value === "";
};

/*
Expand All @@ -203,58 +204,62 @@ const downloadFile = (filename: string, content: string) => {
* This function exports the current data of the chart into either PNG or CSV.
*/
const exportStatisticsData = (): void => {
// Get Chart instance
const chart = Chart.getChart("statistics");
// Get format select field
const exportFormat = document.getElementById("export-format") as HTMLSelectElement;
// Build filename
const filename = `Integreat ${exportFormat.getAttribute("data-filename-prefix")} ${exportLabels[0]} - ${
exportLabels[exportLabels.length - 1]
}`;

if (exportFormat.value === "image") {
const timeoutDuration = 300;
chart.options.plugins.legend.display = true;
chart.update();

// Wait till the legend is fully rendered
setTimeout(() => {
const ctx = chart.canvas.getContext("2d");

if (ctx) {
// This is needed to get a white background for the image. The default background is transparent.
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = "white";
ctx.fillRect(0, 0, chart.canvas.width, chart.canvas.height);

// Capture the chart as a PNG image
const image = chart.toBase64Image();
// Initiate download
downloadFile(`${filename}.png`, image);

chart.options.plugins.legend.display = false;
ctx.globalCompositeOperation = "source-over";
chart.update();
}
}, timeoutDuration);
} else if (exportFormat.value === "csv") {
// Convert datasets into the format [["language 1", "hits on day 1", "hits 2", ...], [["language 1", "hits on day 1", ...], ...]
const datasets = chart.data.datasets;
const datasetsWithLabels: string[][] = datasets
.filter((dataset) => chart.isDatasetVisible(datasets.indexOf(dataset)))
.map((dataset) => [dataset.label].concat(dataset.data.map(String)));
// Ensure export labels don't contain comma and corrupt CSV
exportLabels = exportLabels.map((x) => x.replace(",", " - "));
// Create matrix with date labels in the first row and the hits per language in the subsequent rows
const csvMatrix: string[][] = [[""].concat(exportLabels)].concat(datasetsWithLabels);
// Transpose matrix (swap rows and columns) and join to a single csv string
const csvContent = csvMatrix[0].map((col, i) => csvMatrix.map((row) => row[i]).join(",")).join("\n");
// Initiate download
downloadFile(`${filename}.csv`, `data:text/csv;charset=utf-8;base64,${btoa(csvContent)}`);
} else {
// eslint-disable-next-line no-alert
alert("Export format is not supported.");
console.error("Export format not supported");
// Get kind of statistics to export. If total statistics is requested, proceed.
const exportStatistics = document.getElementById("export-statistics") as HTMLSelectElement;
if (exportStatistics.value === "total-accesses") {
// Get Chart instance
const chart = Chart.getChart("statistics");
// Get format select field
const exportFormat = document.getElementById("export-format") as HTMLSelectElement;
// Build filename
const filename = `Integreat ${exportFormat.getAttribute("data-filename-prefix")} ${exportLabels[0]} - ${
exportLabels[exportLabels.length - 1]
}`;

if (exportFormat.value === "image") {
const timeoutDuration = 300;
chart.options.plugins.legend.display = true;
chart.update();

// Wait till the legend is fully rendered
setTimeout(() => {
const ctx = chart.canvas.getContext("2d");

if (ctx) {
// This is needed to get a white background for the image. The default background is transparent.
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = "white";
ctx.fillRect(0, 0, chart.canvas.width, chart.canvas.height);

// Capture the chart as a PNG image
const image = chart.toBase64Image();
// Initiate download
downloadFile(`${filename}.png`, image);

chart.options.plugins.legend.display = false;
ctx.globalCompositeOperation = "source-over";
chart.update();
}
}, timeoutDuration);
} else if (exportFormat.value === "csv") {
// Convert datasets into the format [["language 1", "hits on day 1", "hits 2", ...], [["language 1", "hits on day 1", ...], ...]
const datasets = chart.data.datasets;
const datasetsWithLabels: string[][] = datasets
.filter((dataset) => chart.isDatasetVisible(datasets.indexOf(dataset)))
.map((dataset) => [dataset.label].concat(dataset.data.map(String)));
// Ensure export labels don't contain comma and corrupt CSV
exportLabels = exportLabels.map((x) => x.replace(",", " - "));
// Create matrix with date labels in the first row and the hits per language in the subsequent rows
const csvMatrix: string[][] = [[""].concat(exportLabels)].concat(datasetsWithLabels);
// Transpose matrix (swap rows and columns) and join to a single csv string
const csvContent = csvMatrix[0].map((col, i) => csvMatrix.map((row) => row[i]).join(",")).join("\n");
// Initiate download
downloadFile(`${filename}.csv`, `data:text/csv;charset=utf-8;base64,${btoa(csvContent)}`);
} else {
// eslint-disable-next-line no-alert
alert("Export format is not supported.");
console.error("Export format not supported");
}
}
};

Expand Down Expand Up @@ -316,4 +321,5 @@ window.addEventListener("load", async () => {

// Event handler for toggling export button
document.getElementById("export-format")?.addEventListener("change", toggleExportButton);
document.getElementById("export-statistics")?.addEventListener("change", toggleExportButton);
});
73 changes: 70 additions & 3 deletions integreat_cms/static/src/js/analytics/statistics-page-accesses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ let statisticsForm: HTMLFormElement;
let pageAccessesURL: string;
let pageAccessesForm: HTMLFormElement;
let ajaxRequestID: number;
let exportTable: string[][];
let visibleDatasetSlugs: string[];

const setAccessBarPerLanguage = (
accessField: Element,
Expand Down Expand Up @@ -89,7 +91,7 @@ const getCheckedSlugs = (): string[] => {
return visibleDatasetSlugs;
};

const updateDOM = (data: AjaxResponse, visibleDatasetSlugs: string[]) => {
const updateDOM = (data: AjaxResponse) => {
const pageNodes = document.querySelectorAll(`.page-row`);
pageNodes.forEach((parentField) => {
const pageId: string = parentField.id.split("-")[1];
Expand Down Expand Up @@ -122,12 +124,34 @@ const updateDOM = (data: AjaxResponse, visibleDatasetSlugs: string[]) => {
});
};

const updateExportTable = (data: AjaxResponse) => {
const pageIds = Object.keys(data);
exportTable = [];
pageIds.forEach((pageId) => {
const exportTableEntry: string[] = [];
exportTableEntry.push(pageId);
const accesses = data[pageId];

let allAccesses: number = 0;
visibleDatasetSlugs?.forEach((languageSlug) => {
if (accesses && accesses[languageSlug]) {
allAccesses += accesses[languageSlug];
exportTableEntry[visibleDatasetSlugs.indexOf(languageSlug) + 1] = String(accesses[languageSlug]);
} else {
exportTableEntry[visibleDatasetSlugs.indexOf(languageSlug) + 1] = "0";
}
});
exportTableEntry[visibleDatasetSlugs.length + 1] = String(allAccesses);
exportTable.push(exportTableEntry);
});
};

/* The main function which updates the accesses */
export const updatePageAccesses = async (): Promise<void> => {
const pageAccessesLoading = document.getElementById("page-accesses-loading");
pageAccessesLoading.classList.remove("hidden");
setDates();
const visibleDatasetSlugs = getCheckedSlugs();
visibleDatasetSlugs = getCheckedSlugs();

ajaxRequestID += 1;
const [data, requestID] = await getData(visibleDatasetSlugs, ajaxRequestID);
Expand All @@ -139,11 +163,51 @@ export const updatePageAccesses = async (): Promise<void> => {
resetTotalAccessesField(accessFields, isEmpty);

if (!isEmpty && requestID === ajaxRequestID) {
updateDOM(data, visibleDatasetSlugs);
updateDOM(data);
updateExportTable(data);
}
pageAccessesLoading.classList.add("hidden");
};

/*
* This function initializes a file download by setting the "href" attribute of the download link to the file data
* and the "download" attribute to the filename.
* After that, a click on the button is simulated.
*/
const downloadFile = (filename: string, content: string) => {
const downloadLink = document.getElementById("export-download-link");
downloadLink.setAttribute("href", content);
downloadLink.setAttribute("download", filename);
downloadLink.click();
};

const exportPageAccessesData = (): void => {
// Get kind of statistics to export. If page access statistics is requested, proceed.
const exportStatistics = document.getElementById("export-statistics") as HTMLSelectElement;
if (exportStatistics.value === "page-accesses") {
// Get format select field
const exportFormat = document.getElementById("export-format") as HTMLSelectElement;
if (exportFormat.value === "csv") {
// Build labels
const exportLabels: string[] = ["ID"].concat(visibleDatasetSlugs).concat(["Total Accesses"]);
// Build filename
const filename = `Integreat ${exportFormat.getAttribute("data-filename-prefix")} ${exportLabels[0]} - ${
exportLabels[exportLabels.length - 1]
}`;
// Create matrix with date labels in the first row and the hits per language in the subsequent rows
const csvMatrix: string[][] = [exportLabels].concat(exportTable);
// Join Matrix to a single csv string
const csvContent = csvMatrix.map((i) => i.join(",")).join("\n");
// Initiate download
downloadFile(`${filename}.csv`, `data:text/csv;charset=utf-8;base64,${btoa(csvContent)}`);
} else {
// eslint-disable-next-line no-alert
alert("Export format is not supported.");
console.error("Export format not supported");
}
}
};

export const setPageAccessesEventListeners = () => {
ajaxRequestID = 0;
statisticsForm = document.getElementById("statistics-form") as HTMLFormElement;
Expand All @@ -156,5 +220,8 @@ export const setPageAccessesEventListeners = () => {
event.preventDefault();
updatePageAccesses();
});
document.getElementById("export-button")?.addEventListener("click", async () => {
exportPageAccessesData();
});
}
};
Loading