Skip to content

Commit

Permalink
Data exporter (#294)
Browse files Browse the repository at this point in the history
  • Loading branch information
HarmlessHarm authored Nov 8, 2024
2 parents 99710fd + 1f069d1 commit f3ba49f
Show file tree
Hide file tree
Showing 10 changed files with 434 additions and 48 deletions.
84 changes: 41 additions & 43 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 8 additions & 5 deletions src/firebase.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';
import 'firebase/storage';
import firebase from "firebase/app";
import "firebase/functions";
import "firebase/auth";
import "firebase/database";
import "firebase/storage";

const config = {
apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
Expand All @@ -20,4 +21,6 @@ const auth = firebase.auth();
const db = firebase.database();
const storage = firebase.storage();

export { firebase, auth, db, storage };
const functions = firebase.functions();

export { firebase, auth, db, storage, functions };
18 changes: 18 additions & 0 deletions src/router/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,24 @@ const routes = [
},
],
},
{
path: "export_csv",
component: {
render(c) {
return c("router-view");
},
},
meta: {
title: "Analytics",
},
children: [
{
path: "",
name: "Export CSV ",
component: () => import("src/views/Admin/ExportCSV.vue"),
},
],
},
{
path: "export",
component: {
Expand Down
61 changes: 61 additions & 0 deletions src/utils/exports/BaseDataExport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
export default class BaseDataExport {
constructor() {
if (new.target == BaseDataExport) {
throw new Error("Cannot instantiate an abstract class.");
}
this.loading = false;
this.header = [];
this.rows = [];
}

startLoading() {
this.loading = false;
}

stopLoading() {
this.loading = false;
}

isLoading() {
return this.loading;
}

getFileName() {
return "dataExport.csv";
}

// Abstract method to retrieve CSV rows
async getCSVRows() {
throw new Error("getCSVRows() must be implemented by subclasses.");
}

// Method to export rows to a CSV file
exportToCSV() {
const filename = this.getFileName();
if (!this.rows || this.rows.length === 0) {
console.error("No data available for CSV export.");
return;
}

const csvRows = [this.header].concat(this.rows);
console.log(this.header, this.rows, csvRows);

// Convert rows array to CSV format
const csvContent = csvRows.map((row) => row.join(",")).join("\n");

// Create a Blob with CSV content and make it downloadable
const blob = new Blob([csvContent], { type: "text/csv" });
const url = URL.createObjectURL(blob);

// Create a link element to trigger the download
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", filename);
document.body.appendChild(link);
link.click();

// Clean up and revoke the object URL
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
}
69 changes: 69 additions & 0 deletions src/utils/exports/SignupsPerDay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { db } from "src/firebase";
import { DefaultDict } from "../generalFunctions";

import BaseDataExport from "./BaseDataExport";

export default class UserDataExport extends BaseDataExport {
constructor() {
super();
this.config = {
label: "Signups Per Day",
value: "signups",
fields: [
{ name: "Date Range", type: "daterange", value: { from: undefined, to: undefined } },
],
};
}

date2timestamp(date_str) {
const date = new Date(date_str);
return date.getTime();
}

timestamp2date(ts) {
const date = new Date(ts);
return date.toLocaleDateString("en-GB");
}

getFileName() {
const range = this.config.fields[0].value;
return `signups_in_range_${range.from}-${range.to}.csv`;
}

// Implementing the abstract getCSVRows method
async getCSVRows() {
this.startLoading();

const from = this.date2timestamp(this.config.fields[0].value.from);
const to = this.date2timestamp(this.config.fields[0].value.to);

this.header = ["date", "count"];
try {
const users = await this.fetchUsersInRange(from, to);
this.rows = await this.aggregateSignups(users);
return this.rows;
} catch (error) {
console.error("Error fetching user data for CSV export:", error);
throw error;
} finally {
this.stopLoading();
}
}

async fetchUsersInRange(from, to) {
const user_ref = db.ref("users").orderByChild("created").startAt(from).endAt(to);
const payload = await user_ref.once("value");
if (!payload.exists()) {
throw new Error("No users found");
}
return Object.values(payload.val());
}

async aggregateSignups(users) {
const aggregate = await users.reduce((counts, user) => {
counts[this.timestamp2date(user.created)] += 1;
return counts;
}, new DefaultDict(0));
return Object.entries(aggregate);
}
}
Loading

0 comments on commit f3ba49f

Please sign in to comment.