Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sheets integration #9

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
45 changes: 21 additions & 24 deletions analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,13 @@ const loopLinks = async () => {
const snapshot = await linksRef.get();
const linksData = [];
snapshot.forEach((doc) => {
linksData.push({course: doc.id, links: doc.data()});
linksData.push({ course: doc.id, links: doc.data() });
})
return linksData;
}

const getUniqueStudents = () => {

const studentsMap = {};
loopStudents().then(res => {
res.forEach(studentSnapshot => {
studentSnapshot.forEach(student => {
studentsMap[student.id] = true;
})
})
const uniqueStudentsList = Object.keys(studentsMap);
// Log all students
console.log(uniqueStudentsList);
// Log the number of unique
console.log(`Number of unique students: ${uniqueStudentsList.length}`);
})
}

const getUniqueCourses = () => {

const coursesMap = {};
loopStudents().then(res => {
res.forEach(studentSnapshot => {
Expand All @@ -49,11 +32,11 @@ const getUniqueCourses = () => {
if (coursesMap[`${course} ${section}`]) {
coursesMap[`${course} ${section}`].count += 1;
} else {
coursesMap[`${course} ${section}`] = {count: 1, hasLink: false}
coursesMap[`${course} ${section}`] = { count: 1, hasLink: false }
}
})
})

// Loop through links to see they exist for a course
loopLinks().then(res => {
res.forEach(courseObj => {
Expand All @@ -66,7 +49,7 @@ const getUniqueCourses = () => {
// Build array to visualize most popular courses
const classesArray = [];
Object.entries(coursesMap).forEach(([className, info]) => {
classesArray.push({className: className, count: info.count, hasLink: info.hasLink})
classesArray.push({ className: className, count: info.count, hasLink: info.hasLink })
})
// Sort list
classesArray.sort((a, b) => (a.count > b.count) ? 1 : -1);
Expand All @@ -78,6 +61,20 @@ const getUniqueCourses = () => {
})
}

const getUniqueStudents = () => {
let students = new Set()
db.collectionGroup('students')
.get()
.then(snapshot => {
snapshot.docs.forEach(doc => students.add(doc.id))
return students
})
.then(set => {
console.log(set)
console.log(`Number of unique students: ${set.size}`)
})
}

// Run functions to console log analytics
// getUniqueStudents();
// getUniqueCourses();
getUniqueStudents()
getUniqueCourses();
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"antd": "^4.1.1",
"firebase": "^7.14.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
Expand Down
13 changes: 12 additions & 1 deletion client/src/App.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
// Here's to 100 commits and a badass project

import React from 'react';
import * as firebase from 'firebase/app'
import "firebase/analytics";
import { Layout, Divider } from 'antd';
import { BellTwoTone } from '@ant-design/icons';

import Window from './components/Window';
import Fields from './components/Fields';
import * as dotenv from 'dotenv'

import './App.css';

dotenv.config()
const analytics = firebase.analytics;
const firebaseConfig = JSON.parse(process.env.FBCONFIG)

firebase.initializeApp(firebaseConfig)

const { Header, Content, Footer } = Layout;

analytics().setCurrentScreen(window.location.pathname) // sets `screen_name` parameter
analytics().logEvent('screen_view') // log event with `screen_name` parameter attached

function App() {
return (
<div>
Expand Down
2 changes: 1 addition & 1 deletion db-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ function handleNewLink(course, section, link) {
return new Promise(async (resolve, reject) => {

const courseRef = await db.collection('zoomLinks').doc(course);
courseRef.set({[section]: link}, {merge: true}).then((res) => {
courseRef.set({ [section]: link }, { merge: true }).then((res) => {
resolve(res);
}).catch((err) => {
reject(err);
Expand Down
2 changes: 1 addition & 1 deletion functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,5 @@ exports.scheduledEmailSend = functions.pubsub.schedule('*/5 7-22 * * *')
.timeZone('America/New_York')
.onRun(async (context) => {
console.log('sending emails')
await sendEmails()
return sendEmails()
})
184 changes: 177 additions & 7 deletions functions/sheets.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');
require('dotenv').config();
const firebase = require('./db-config')

const db = firebase.admin.firestore()

// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';

// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Sheets API.
authorize(JSON.parse(content), listMajors);
});
// Load the sheets API credentials
const sheetsCredentials = process.env.SHEETS_CREDENTIALS

// call the script.
authorize(JSON.parse(sheetsCredentials), uploadToFirebase)

// store the ID of our sheet
const sheetID = '1pFQNnNGhzM0OJhFTCXoSdSv1JDDuGrNzYH9YxlwNnJc'
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
Expand Down Expand Up @@ -65,3 +70,168 @@ function getNewToken(oAuth2Client, callback) {
});
});
}

/**
* Get all the meeting IDs stored on the google sheets to transform to a zoom link
*
* @param {*} auth: The authorization credentials allowing us to interface with the
* sheets API
*/
function getMeetingLinks(auth) {
return new Promise((resolve, reject) => {
// Initialize google sheets
const sheets = google.sheets({ version: 'v4', auth });

// idLinks will contain the objects of the class with meeting id link with the cell it was stored in
let idLinks = []

sheets.spreadsheets.values
.get({
spreadsheetId: sheetID,

// get all the columns where the links and meeting IDs are stored
range: 'LinkFormData!E2:E',
}, (err, res) => {
if (err) return reject(err);

// get all the rows
const rows = res.data.values;

if (rows.length) {
rows.map((row, index) => {
// TODO: change this check.

// check for empty rows
if (row.length) {
console.log(row)
if (/[0-9]{3}-[0-9]{3}-[0-9]{3,5}/.test(row[0])) {
// Push the meetingID and corresponding cell onto iddLinks
idLinks.push({ meetingID: row[0], cell: `E${2 + index}`, toUpdate: true })
} else if (row[0].includes('cornell.zoom.us') || row[0].includes('canvas.cornell.edu')) {
idLinks.push({ meetingID: row[0], cell: `E${2 + index}`, toUpdate: false })
}
}
});
} else {
reject(err);
}
console.log(idLinks)
resolve(idLinks)
})
.catch(err => console.log(err))
})
}

function updateMeetingToLink(auth) {
const sheets = google.sheets({ version: 'v4', auth });
// Get all the entries where meeting IDs have been entered instead of zoom links
return new Promise((resolve, reject) => {
getMeetingLinks(auth)
.then(idLinks => {
let dataArr = []
idLinks.forEach(({ meetingID, cell, toUpdate }) => {
// reformat meeting ID to zoom link (if it needs to be updated)
let meetingLink = toUpdate ? 'https://cornell.zoom.us/j/' + meetingID.split('-').join('') : meetingID

// set the value to enter onto the cell
const value = [[meetingLink]]

// Reformat the cell value to A1 notation
const cellInput = `LinkFormData!${cell}:${cell}`

// Set the data config for this specific entry
const data = {
range: cellInput,
majorDimension: "ROWS",
values: value
}
dataArr.push(data)
})

const resource = {
valueInputOption: 'RAW',
data: dataArr
}

// batch update all the entries that need updating
sheets.spreadsheets.values.batchUpdate({
spreadsheetId: sheetID,
resource: resource
}, (err, _) => {
if (err) return console.log(err);
console.log('successfully updated')
resolve()
})

return true
})
.catch(err => reject(err))
})
}

function addNewLink(course, section, link) {
return new Promise(async (resolve, reject) => {

const courseRef = await db.collection('unconfirmedLinks').doc(course);
courseRef.set({ [section]: link }, { merge: true }).then((res) => {
return resolve(res);
}).catch((err) => {
reject(err);
});
})
}

async function uploadToFirebase(auth) {
const _ = await updateMeetingToLink(auth)
const sheets = google.sheets({ version: 'v4', auth });
sheets.spreadsheets.values
.get({
spreadsheetId: sheetID,

// get all the columns where the links and meeting IDs are stored
range: 'LinkFormData!A2:E',
}, (err, res) => {
if (err) return console.log('Error in uploadToFirebase: ' + err)

// get all the rows from spreadsheet
const rows = res.data.values;

if (rows.length) {
rows.forEach(async row => {
if (row.length) {
const courseCode = row[2]
const sectionCode = row[3]
const zoomLink = row[4]
// add the zoom link corresponding to the appropriate class + section to firebase
const _ = await addNewLink(courseCode, sectionCode, zoomLink)
}
})

// Delete all rows, 'refresh' spreadsheet
sheets.spreadsheets.batchUpdate({
spreadsheetId: sheetID,
resource: {

"requests": [
{
"deleteRange": {
"range": {
"sheetId": "545073997",
"startRowIndex": "2",
"endRowIndex": 100
},
"shiftDimension": "ROWS"
}
}
]
}
}, (err, _) => {
if (err) return console.log(err)
})


} else {
console.log('no data found.')
}
})
}
1 change: 1 addition & 0 deletions functions/token.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"access_token":"ya29.a0Ae4lvC3nE14lR_DPCebt8lYK8tDUXbRqwUM9ZFPFqyyPSCGNOi4i3D6-kftT5S1rCmu--YkqNA4d8BYa0FLduUDG7Gc-mMdURh3V3c20TirmmPINHiFzFGy953TL4Dx54rLoFiqmTMR9_7uLb1yTLIVBEscOyIghVqM","refresh_token":"1//0ga_bU5ZL1iiFCgYIARAAGBASNwF-L9IrdOqfuHFgAPydEBUkDoKRdIn28Juzoz6JceNm3mKhJLc17TcXTb5T8ibQF1FIsVi0ckI","scope":"https://www.googleapis.com/auth/spreadsheets","token_type":"Bearer","expiry_date":1587291068683}