Browse Source

feat: worked on youtube quota chart data

Kristian Vos 2 years ago
parent
commit
aabe7ac04c
3 changed files with 174 additions and 65 deletions
  1. 18 2
      backend/logic/actions/youtube.js
  2. 133 60
      backend/logic/youtube.js
  3. 23 3
      frontend/src/pages/Admin/YouTube/index.vue

+ 18 - 2
backend/logic/actions/youtube.js

@@ -32,10 +32,26 @@ export default {
 	/**
 	 * Returns YouTube quota chart data
 	 *
+	 * @param {object} session - the session object automatically added by the websocket
+	 * @param timePeriod - either hours or days
+	 * @param startDate - beginning date
+	 * @param endDate - end date
+	 * @param dataType - either usage or count
 	 * @returns {{status: string, data: object}}
 	 */
-	getQuotaChartData: isAdminRequired(function getQuotaChartData(session, cb) {
-		YouTubeModule.runJob("GET_QUOTA_CHART_DATA", {}, this)
+	getQuotaChartData: isAdminRequired(function getQuotaChartData(
+		session,
+		timePeriod,
+		startDate,
+		endDate,
+		dataType,
+		cb
+	) {
+		YouTubeModule.runJob(
+			"GET_QUOTA_CHART_DATA",
+			{ timePeriod, startDate: new Date(startDate), endDate: new Date(endDate), dataType },
+			this
+		)
 			.then(data => {
 				this.log("SUCCESS", "YOUTUBE_GET_QUOTA_CHART_DATA", `Getting quota chart data was successful.`);
 				return cb({ status: "success", data });

+ 133 - 60
backend/logic/youtube.js

@@ -280,84 +280,157 @@ class _YouTubeModule extends CoreClass {
 	/**
 	 * Returns YouTube quota chart data
 	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.timePeriod - either hours or days
+	 * @param {string} payload.startDate - beginning date
+	 * @param {string} payload.endDate - end date
+	 * @param {string} payload.dataType - either usage or count
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
-	GET_QUOTA_CHART_DATA() {
+	GET_QUOTA_CHART_DATA(payload) {
 		return new Promise((resolve, reject) => {
+			const { timePeriod, startDate, endDate, dataType } = payload;
+
+			// const timePeriod = "hours";
+			// const startDate = new Date("2022-05-20 00:00:00");
+			// const endDate = new Date("2022-05-21 00:00:00");
+
+			// const timePeriod = "days";
+			// const startDate = new Date("2022-05-15 00:00:00");
+			// const endDate = new Date("2022-05-21 00:00:00");
+			// const endDate = new Date("2022-05-30 00:00:00");
+
+			// const dataType = "usage";
+			// const dataType = "count";
+
 			async.waterfall(
 				[
 					next => {
-						const fromDate = new Date(new Date() - 6 * 24 * 60 * 60 * 1000);
-						fromDate.setUTCHours(0, 0, 0);
-						const dates = [];
-						const tempDate = new Date(fromDate.getTime());
-						while (dates.length < 7) {
-							dates.push(new Date(tempDate));
-							tempDate.setDate(tempDate.getDate() + 1);
+						let timeRanges = [];
+						const lastDate = new Date(startDate);
+						if (timePeriod === "hours") {
+							startDate.setMinutes(0, 0, 0);
+							endDate.setMinutes(0, 0, 0);
+							do {
+								const newDate = new Date(lastDate.getTime() + 1000 * 60 * 60);
+
+								timeRanges.push({
+									startDate: new Date(lastDate),
+									endDate: newDate
+								});
+
+								lastDate.setTime(newDate.getTime());
+							} while (lastDate.getTime() < endDate.getTime());
+							if (timeRanges.length === 0 || timeRanges.length > 24)
+								return next("No valid time ranges specified.");
+							timeRanges = timeRanges.map(timeRange => ({
+								...timeRange,
+								label: `${timeRange.startDate.getHours().toString().padStart(2, "0")}:00`
+							}));
+						} else if (timePeriod === "days") {
+							startDate.setHours(0, 0, 0, 0);
+							endDate.setHours(0, 0, 0, 0);
+							do {
+								const newDate = new Date(lastDate.getTime() + 1000 * 60 * 60 * 24);
+
+								timeRanges.push({
+									startDate: new Date(lastDate),
+									endDate: newDate
+								});
+
+								lastDate.setTime(newDate.getTime());
+							} while (lastDate.getTime() < endDate.getTime());
+							if (timeRanges.length === 0 || timeRanges.length > 31)
+								return next("No valid time ranges specified.");
+							const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+							if (timeRanges.length <= 7)
+								timeRanges = timeRanges.map(timeRange => ({
+									...timeRange,
+									label: days[timeRange.startDate.getDay()]
+								}));
+							else
+								timeRanges = timeRanges.map(timeRange => ({
+									...timeRange,
+									label: `${timeRange.startDate.getDate().toString().padStart(2, "0")}-${(
+										timeRange.startDate.getMonth() + 1
+									)
+										.toString()
+										.padStart(2, "0")}`
+								}));
 						}
-						next(null, fromDate, dates);
+
+						return next(null, timeRanges);
 					},
 
-					(fromDate, dates, next) => {
+					(timeRanges, next) => {
 						YouTubeModule.youtubeApiRequestModel
-							.aggregate([
-								{
-									$match: { date: { $gte: fromDate } }
-								},
-								{
-									$group: {
-										_id: { $dateToString: { format: "%Y-%m-%d", date: "$date" } },
-										usage: { $sum: "$quotaCost" },
-										count: { $sum: 1 }
-									}
-								},
-								{
-									$sort: { _id: 1 }
-								},
-								{
-									$project: { date: "$_id", usage: 1, count: 1 }
-								}
-							])
-							.exec((err, data) => {
-								if (err) next(err);
-								else next(null, dates, data);
+							.find({
+								date: { $gte: startDate, $lte: endDate }
+							})
+							.sort({ date: 1 })
+							.exec((err, youtubeApiRequests) => {
+								next(err, timeRanges, youtubeApiRequests);
 							});
 					},
 
-					(dates, data, next) => {
-						const filteredData = dates.map(date => {
-							const dayData = data.find(row => new Date(row.date).getDate() === date.getDate());
-							if (dayData) return { date, usage: dayData.usage, count: dayData.count };
-							return { date, usage: 0, count: 0 };
-						});
-						const friendlyDates = dates.map(date => date.toISOString().split("T")[0]);
-						next(null, {
-							quotaUsage: {
-								labels: friendlyDates,
-								datasets: [
-									{
-										label: "All",
-										data: filteredData.map(row => row.usage),
-										borderColor: "rgb(2, 166, 242)"
-									}
-								]
-							},
-							apiRequests: {
-								labels: friendlyDates,
-								datasets: [
-									{
-										label: "All",
-										data: filteredData.map(row => row.count),
-										borderColor: "rgb(2, 166, 242)"
+					(timeRanges, youtubeApiRequests, next) => {
+						const regex = /https:\/\/www\.googleapis\.com\/youtube\/v3\/(.+)/;
+						const requestTypes = Object.fromEntries(
+							youtubeApiRequests
+								.map(youtubeApiRequest => regex.exec(youtubeApiRequest.url)[1])
+								.filter((requestType, index, array) => array.indexOf(requestType) === index)
+								.map(requestType => [requestType, 0])
+						);
+						timeRanges = timeRanges.map(timeRange => ({
+							...timeRange,
+							data: { total: 0, ...requestTypes }
+						}));
+
+						youtubeApiRequests.forEach(youtubeApiRequest => {
+							timeRanges.forEach(timeRange => {
+								if (
+									timeRange.startDate <= youtubeApiRequest.date &&
+									timeRange.endDate >= youtubeApiRequest.date
+								) {
+									const requestType = regex.exec(youtubeApiRequest.url)[1];
+									if (!timeRange.data[requestType]) timeRange.data[requestType] = 0;
+
+									if (dataType === "usage") {
+										timeRange.data[requestType] += youtubeApiRequest.quotaCost;
+										timeRange.data.total += youtubeApiRequest.quotaCost;
+									} else if (dataType === "count") {
+										timeRange.data[requestType] += 1;
+										timeRange.data.total += 1;
 									}
-								]
-							}
+								}
+							});
 						});
+
+						next(null, timeRanges);
+					},
+
+					(timeRanges, next) => {
+						const chartData = {};
+						chartData.labels = timeRanges.map(timeRange => timeRange.label);
+						const datasetTypes = Object.keys(timeRanges[0].data);
+						const colors = {
+							total: "rgb(2, 166, 242)",
+							videos: "rgb(166, 2, 242)",
+							search: "rgb(242, 2, 166)",
+							channels: "rgb(2, 242, 166)",
+							playlistItems: "rgb(242, 166, 2)"
+						};
+						chartData.datasets = datasetTypes.map(datasetType => ({
+							label: datasetType,
+							backgroundColor: colors[datasetType],
+							data: timeRanges.map(timeRange => timeRange.data[datasetType])
+						}));
+						next(null, chartData);
 					}
 				],
-				(err, data) => {
+				(err, chartData) => {
 					if (err) reject(err);
-					else resolve(data);
+					else resolve(chartData);
 				}
 			);
 		});

+ 23 - 3
frontend/src/pages/Admin/YouTube/index.vue

@@ -272,9 +272,29 @@ export default {
 				}
 			);
 
-			this.socket.dispatch("youtube.getQuotaChartData", res => {
-				if (res.status === "success") this.charts = res.data;
-			});
+			this.socket.dispatch(
+				"youtube.getQuotaChartData",
+				"days",
+				new Date().setDate(new Date().getDate() - 6),
+				new Date().setDate(new Date().getDate() + 1),
+				"usage",
+				res => {
+					if (res.status === "success")
+						this.charts.quotaUsage = res.data;
+				}
+			);
+
+			this.socket.dispatch(
+				"youtube.getQuotaChartData",
+				"days",
+				new Date().setDate(new Date().getDate() - 6),
+				new Date().setDate(new Date().getDate() + 1),
+				"count",
+				res => {
+					if (res.status === "success")
+						this.charts.apiRequests = res.data;
+				}
+			);
 		},
 		getDateFormatted(createdAt) {
 			const date = new Date(createdAt);