From dde172b1f878558c6b11bcf0f6374c6747365abf Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Sun, 31 May 2020 20:36:43 +0200 Subject: [PATCH 01/13] feat(stats): Creation of a page for stats exploration --- frontend/src/components/pages/PageStats.jsx | 208 +++++++++++++++++--- 1 file changed, 181 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/pages/PageStats.jsx b/frontend/src/components/pages/PageStats.jsx index 8f4b2a83..6806b3a3 100644 --- a/frontend/src/components/pages/PageStats.jsx +++ b/frontend/src/components/pages/PageStats.jsx @@ -1,55 +1,209 @@ -import React from "react"; +import React, { useCallback, useState } from "react"; import { compose } from "recompose"; import Typography from "@material-ui/core/Typography"; import { Line } from "react-chartjs-2"; import alasql from "alasql"; +import { makeStyles } from "@material-ui/styles"; +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import Paper from "@material-ui/core/Paper"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableContainer from "@material-ui/core/TableContainer"; +import TableHead from "@material-ui/core/TableHead"; +import TablePagination from "@material-ui/core/TablePagination"; +import TableRow from "@material-ui/core/TableRow"; import { withPaddedPaper } from "./shared"; const data = [ { a: 1, b: 1, c: 1 }, { a: 1, b: 2, c: 1 }, { a: 1, b: 3, c: 1 }, - { a: 2, b: 1, c: 1 }, + { a: 2, b: 1, c: 1 } ]; -const res = alasql("SELECT a, COUNT(*) AS c FROM ? GROUP BY a", [data]); -// eslint-disable-next-line no-console -console.log(res); +const useStyles = makeStyles({ + root: { + width: "100%" + }, + container: { + maxHeight: 440 + } +}); + +function TableFromData({ data }) { + const classes = useStyles(); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(10); + + if (data.length === 0) { + return <>; + } + + const columns = Object.keys(data[0]).map(id => ({ id, label: id })); + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = event => { + setRowsPerPage(+event.target.value); + setPage(0); + }; + + return ( + + + + + + {columns.map(column => ( + {column.label} + ))} + + + + {data + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row, i) => { + return ( + + {columns.map(column => { + const value = row[column.id]; + return ( + + {column.format && typeof value === "number" + ? column.format(value) + : value} + + ); + })} + + ); + })} + +
+
+ +
+ ); +} + +function executeRequest(sqlRequest, data) { + return alasql.promise(sqlRequest, [data]); +} /** * Component corresponding to the stats page of the site */ function PageStats() { + const classes = useStyles(); + const [sqlRequest, setSqlRequest] = useState(""); + const [requestError, setRequestError] = useState(""); + const [requestResult, setRequestResult] = useState([]); + return ( <> Vive les stats -
- More to come... - setSqlRequest(event.target.value)} /> + {requestError !== "" && ( + <> +

+ {requestError.toString()} +

+ + )} + + {requestResult.length !== 0 && ( + <> +

+ +

+ + + )} ); } PageStats.propTypes = {}; +/** + * SELECT ts as x, nb_connections as y + * + * SELECT ts as x, branche as cat, nb_connections as y + */ + +// {/* */} + export default compose(withPaddedPaper())(PageStats); -- GitLab From ffdb5939fcd26836dca0d804514c28887977dd0a Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Fri, 5 Jun 2020 18:31:36 +0200 Subject: [PATCH 02/13] feat(stats): refactor & clean code --- frontend/src/components/pages/PageStats.jsx | 195 +----------------- .../components/stats/RequestSQLHandler.jsx | 133 ++++++++++++ frontend/src/components/stats/Table.jsx | 89 ++++++++ 3 files changed, 231 insertions(+), 186 deletions(-) create mode 100644 frontend/src/components/stats/RequestSQLHandler.jsx create mode 100644 frontend/src/components/stats/Table.jsx diff --git a/frontend/src/components/pages/PageStats.jsx b/frontend/src/components/pages/PageStats.jsx index 6806b3a3..e58cb4ef 100644 --- a/frontend/src/components/pages/PageStats.jsx +++ b/frontend/src/components/pages/PageStats.jsx @@ -1,209 +1,32 @@ -import React, { useCallback, useState } from "react"; +import React, { useState } from "react"; import { compose } from "recompose"; import Typography from "@material-ui/core/Typography"; -import { Line } from "react-chartjs-2"; -import alasql from "alasql"; import { makeStyles } from "@material-ui/styles"; -import TextField from "@material-ui/core/TextField"; -import Button from "@material-ui/core/Button"; -import Paper from "@material-ui/core/Paper"; -import Table from "@material-ui/core/Table"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableContainer from "@material-ui/core/TableContainer"; -import TableHead from "@material-ui/core/TableHead"; -import TablePagination from "@material-ui/core/TablePagination"; -import TableRow from "@material-ui/core/TableRow"; import { withPaddedPaper } from "./shared"; - -const data = [ - { a: 1, b: 1, c: 1 }, - { a: 1, b: 2, c: 1 }, - { a: 1, b: 3, c: 1 }, - { a: 2, b: 1, c: 1 } -]; +import SqlInterface from "../stats/RequestSQLHandler"; const useStyles = makeStyles({ - root: { - width: "100%" - }, - container: { - maxHeight: 440 - } + requestInterface: {} }); -function TableFromData({ data }) { - const classes = useStyles(); - const [page, setPage] = React.useState(0); - const [rowsPerPage, setRowsPerPage] = React.useState(10); - - if (data.length === 0) { - return <>; - } - - const columns = Object.keys(data[0]).map(id => ({ id, label: id })); - - const handleChangePage = (event, newPage) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = event => { - setRowsPerPage(+event.target.value); - setPage(0); - }; - - return ( - - - - - - {columns.map(column => ( - {column.label} - ))} - - - - {data - .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((row, i) => { - return ( - - {columns.map(column => { - const value = row[column.id]; - return ( - - {column.format && typeof value === "number" - ? column.format(value) - : value} - - ); - })} - - ); - })} - -
-
- -
- ); -} - -function executeRequest(sqlRequest, data) { - return alasql.promise(sqlRequest, [data]); -} - /** * Component corresponding to the stats page of the site */ function PageStats() { const classes = useStyles(); - const [sqlRequest, setSqlRequest] = useState(""); - const [requestError, setRequestError] = useState(""); - const [requestResult, setRequestResult] = useState([]); return ( <> - Vive les stats - setSqlRequest(event.target.value)} - /> - {requestError !== "" && ( - <> -

- {requestError.toString()} -

- - )} - - {requestResult.length !== 0 && ( - <> -

- -

- - - )} + + Exploration des statistiques d'utilisation du site REX-DRI + +
+ +
); } PageStats.propTypes = {}; -/** - * SELECT ts as x, nb_connections as y - * - * SELECT ts as x, branche as cat, nb_connections as y - */ - -// {/* */} - export default compose(withPaddedPaper())(PageStats); diff --git a/frontend/src/components/stats/RequestSQLHandler.jsx b/frontend/src/components/stats/RequestSQLHandler.jsx new file mode 100644 index 00000000..eeb463cc --- /dev/null +++ b/frontend/src/components/stats/RequestSQLHandler.jsx @@ -0,0 +1,133 @@ +import alasql from "alasql"; +import React, { useState } from "react"; +import Typography from "@material-ui/core/Typography"; +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import TableFromData from "./Table"; +import { makeStyles } from "@material-ui/styles"; + +const data = [ + { a: 1, b: 1, c: 1 }, + { a: 1, b: 2, c: 1 }, + { a: 1, b: 3, c: 1 }, + { a: 2, b: 1, c: 1 } +]; + +/* SELECT a, COUNT(*) AS c FROM ? GROUP BY a + SELECT a as x, COUNT(*) AS y FROM ? GROUP BY a */ + +const useStyles = makeStyles({}); + +function executeRequest(sqlRequest, data) { + return alasql.promise(sqlRequest, [data]); +} + +function plotResult(result) { + console.log(result); + + // {/* */} +} + +function SqlRequestResult({ result }) { + return ( +
+

+ +

+ + +
+ ); +} + +function SqlInterface() { + const classes = useStyles(); + const [sqlRequest, setSqlRequest] = useState(""); + const [requestError, setRequestError] = useState(""); + const [requestResult, setRequestResult] = useState([]); + return ( +
+ setSqlRequest(event.target.value)} + /> + {requestError !== "" && ( + <> +

+ {requestError.toString()} +

+ + )} + + {requestResult.length !== 0 && ( + + )} +
+ ); +} + +export default SqlInterface; diff --git a/frontend/src/components/stats/Table.jsx b/frontend/src/components/stats/Table.jsx new file mode 100644 index 00000000..22ef0b5e --- /dev/null +++ b/frontend/src/components/stats/Table.jsx @@ -0,0 +1,89 @@ +import React from "react"; +import Paper from "@material-ui/core/Paper"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableContainer from "@material-ui/core/TableContainer"; +import TableHead from "@material-ui/core/TableHead"; +import TablePagination from "@material-ui/core/TablePagination"; +import TableRow from "@material-ui/core/TableRow"; +import { makeStyles } from "@material-ui/styles"; + +const useStyles = makeStyles({ + root: { + width: "100%" + }, + container: { + maxHeight: 440 + } +}); + +function TableFromData({ data }) { + const classes = useStyles(); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(10); + + if (data.length === 0) { + return <>; + } + + const columns = Object.keys(data[0]).map(id => ({ id, label: id })); + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = event => { + setRowsPerPage(+event.target.value); + setPage(0); + }; + + return ( + + + + + + {columns.map(column => ( + {column.label} + ))} + + + + {data + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row, i) => { + return ( + + {columns.map(column => { + const value = row[column.id]; + return ( + + {column.format && typeof value === "number" + ? column.format(value) + : value} + + ); + })} + + ); + })} + +
+
+ +
+ ); +} + +export default TableFromData; -- GitLab From c82ead0a9bead84f8effdce4f9ff488aede1c8cb Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Fri, 5 Jun 2020 20:19:00 +0200 Subject: [PATCH 03/13] feat(stats): Creation of a plot --- .../components/stats/RequestSQLHandler.jsx | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/stats/RequestSQLHandler.jsx b/frontend/src/components/stats/RequestSQLHandler.jsx index eeb463cc..ba755335 100644 --- a/frontend/src/components/stats/RequestSQLHandler.jsx +++ b/frontend/src/components/stats/RequestSQLHandler.jsx @@ -5,11 +5,16 @@ import TextField from "@material-ui/core/TextField"; import Button from "@material-ui/core/Button"; import TableFromData from "./Table"; import { makeStyles } from "@material-ui/styles"; +import { Line } from "react-chartjs-2"; const data = [ { a: 1, b: 1, c: 1 }, - { a: 1, b: 2, c: 1 }, + { a: 4, b: 2, c: 1 }, { a: 1, b: 3, c: 1 }, + { a: 3, b: 4, c: 5 }, + { a: 2, b: 3, c: 2 }, + { a: 1, b: 3, c: 1 }, + { a: 4, b: 3, c: 3 }, { a: 2, b: 1, c: 1 } ]; @@ -22,33 +27,37 @@ function executeRequest(sqlRequest, data) { return alasql.promise(sqlRequest, [data]); } -function plotResult(result) { - console.log(result); +function PlotResult({ result }) { + result.sort((a, b) => Object.values(a) > Object.values(b)); - // {/* */} + if ( + !Object.keys(result[0]).includes("x") || + !Object.keys(result[0]).includes("y") + ) { + return
Erreur : Vous devez préciser x et y.
; + } + return ( +
+ r.x), + datasets: [ + { + label: "My First dataset", //séparer par cat + backgroundColor: "rgb(255, 99, 132)", + borderColor: "rgb(255, 99, 132)", + data: result.map(r => r.y) + } + ] + }} + /> +
+ ); } function SqlRequestResult({ result }) { + const [resultToPlot, setResultToPlot] = useState([]); + return (


@@ -70,11 +79,12 @@ function SqlRequestResult({ result }) { variant="outlined" color={"secondary"} onClick={() => { - plotResult(result); + setResultToPlot(result); }} > Afficher les résultats graphiquement + {resultToPlot.length !== 0 && }
); } @@ -118,7 +128,7 @@ function SqlInterface() { .catch(err => { setRequestError(err); }); - console.log(btoa(sqlRequest)); + console.log(btoa(sqlRequest)); //pour encodage de l'url }} > Exécuter -- GitLab From 277d16d87e6136255462712f17342663e148f7fd Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Sat, 6 Jun 2020 18:39:38 +0200 Subject: [PATCH 04/13] feat(stats): division of the data in two separate datasets and request added to URL --- backend/base_app/templates/stats.html | 5 +- backend/base_app/views.py | 29 ++++++++- .../components/stats/RequestSQLHandler.jsx | 61 ++++++++++++++----- frontend/src/components/stats/Table.jsx | 2 + 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/backend/base_app/templates/stats.html b/backend/base_app/templates/stats.html index 75894277..4504513d 100644 --- a/backend/base_app/templates/stats.html +++ b/backend/base_app/templates/stats.html @@ -1,11 +1,12 @@ {% extends "base.html" %} + {% load render_bundle from webpack_loader %} {% block content %} - + {% render_bundle 'stats' %} {% endblock %} diff --git a/backend/base_app/views.py b/backend/base_app/views.py index 3293e7df..b6ed8f37 100644 --- a/backend/base_app/views.py +++ b/backend/base_app/views.py @@ -50,7 +50,34 @@ def stats(request): """ Render the view that displays stats """ - stats_data = [] + dataset = request.GET.get("dataset") + + if dataset == "1": + stats_data = [ + { 'a': 1, 'b': 1, 'c': 1 }, + { 'a': 4, 'b': 2, 'c': 1 }, + { 'a': 1, 'b': 3, 'c': 1 }, + { 'a': 3, 'b': 4, 'c': 5 }, + { 'a': 2, 'b': 3, 'c': 2 }, + { 'a': 1, 'b': 3, 'c': 1 }, + { 'a': 4, 'b': 3, 'c': 3 }, + { 'a': 2, 'b': 1, 'c': 1 } + ] + elif dataset == "2": + stats_data = [ + { 'f': 1, 'g': 1, 'h': 1 }, + { 'f': 4, 'g': 2, 'h': 1 }, + { 'f': 1, 'g': 3, 'h': 1 }, + { 'f': 3, 'g': 4, 'h': 5 }, + { 'f': 2, 'g': 3, 'h': 2 }, + { 'f': 1, 'g': 3, 'h': 1 }, + { 'f': 4, 'g': 3, 'h': 3 }, + { 'f': 2, 'g': 1, 'h': 1 } + ] + else: + return HttpResponseRedirect("/stats/?dataset=1") + + return render(request, "stats.html", dict(stats_data=json.dumps(stats_data))) diff --git a/frontend/src/components/stats/RequestSQLHandler.jsx b/frontend/src/components/stats/RequestSQLHandler.jsx index ba755335..d2cee6f5 100644 --- a/frontend/src/components/stats/RequestSQLHandler.jsx +++ b/frontend/src/components/stats/RequestSQLHandler.jsx @@ -1,5 +1,5 @@ import alasql from "alasql"; -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import Typography from "@material-ui/core/Typography"; import TextField from "@material-ui/core/TextField"; import Button from "@material-ui/core/Button"; @@ -7,16 +7,7 @@ import TableFromData from "./Table"; import { makeStyles } from "@material-ui/styles"; import { Line } from "react-chartjs-2"; -const data = [ - { a: 1, b: 1, c: 1 }, - { a: 4, b: 2, c: 1 }, - { a: 1, b: 3, c: 1 }, - { a: 3, b: 4, c: 5 }, - { a: 2, b: 3, c: 2 }, - { a: 1, b: 3, c: 1 }, - { a: 4, b: 3, c: 3 }, - { a: 2, b: 1, c: 1 } -]; +const data = __StatsData; /* SELECT a, COUNT(*) AS c FROM ? GROUP BY a SELECT a as x, COUNT(*) AS y FROM ? GROUP BY a */ @@ -28,7 +19,7 @@ function executeRequest(sqlRequest, data) { } function PlotResult({ result }) { - result.sort((a, b) => Object.values(a) > Object.values(b)); + result.sort((a, b) => Object.values(a) > Object.values(b)); //demander à l'utilisateur de faire un order by if ( !Object.keys(result[0]).includes("x") || @@ -89,11 +80,54 @@ function SqlRequestResult({ result }) { ); } +const defaultRequest = "SELECT a as x, COUNT(*) AS y FROM ? GROUP BY a;"; + +function getRequestFromUrl() { + const getParamsInUrl = new URL(document.location).searchParams; + let request = defaultRequest; + + try { + const requestInfoString = getParamsInUrl.has("request_info") + ? getParamsInUrl.get("request_info") + : ""; + request = JSON.parse(atob(requestInfoString)).request; + } catch (err) { + console.log(err); + } + + return request; +} + +/** + * + */ +function setRequestInUrl(request) { + const requestInfo = { + request, + version: 1.0 + }; + + const requestInfoAsString = btoa(JSON.stringify(requestInfo)); + const getParamsInUrl = new URL(document.location).searchParams; + getParamsInUrl.set("request_info", requestInfoAsString); + + window.history.replaceState(null, null, `?${getParamsInUrl.toString()}`); +} + +const requestFromUrl = getRequestFromUrl(); + function SqlInterface() { const classes = useStyles(); - const [sqlRequest, setSqlRequest] = useState(""); + + const [sqlRequest, setSqlRequest] = useState(requestFromUrl); + + useEffect(() => { + setRequestInUrl(sqlRequest); + }, [sqlRequest]); + const [requestError, setRequestError] = useState(""); const [requestResult, setRequestResult] = useState([]); + return (
{ setRequestError(err); }); - console.log(btoa(sqlRequest)); //pour encodage de l'url }} > Exécuter diff --git a/frontend/src/components/stats/Table.jsx b/frontend/src/components/stats/Table.jsx index 22ef0b5e..d28fc617 100644 --- a/frontend/src/components/stats/Table.jsx +++ b/frontend/src/components/stats/Table.jsx @@ -86,4 +86,6 @@ function TableFromData({ data }) { ); } +// TODO rajouter proptypes + export default TableFromData; -- GitLab From f07e6c190f47a2917655c097b519f14bab7f97e6 Mon Sep 17 00:00:00 2001 From: Florent Chehab Date: Sun, 7 Jun 2020 22:52:03 +0200 Subject: [PATCH 05/13] feat(backend): Added concrete example data for stats --- backend/base_app/views.py | 217 ++++++++++++++++-- .../components/stats/RequestSQLHandler.jsx | 36 +-- 2 files changed, 221 insertions(+), 32 deletions(-) diff --git a/backend/base_app/views.py b/backend/base_app/views.py index b6ed8f37..d608de89 100644 --- a/backend/base_app/views.py +++ b/backend/base_app/views.py @@ -53,31 +53,210 @@ def stats(request): dataset = request.GET.get("dataset") if dataset == "1": - stats_data = [ - { 'a': 1, 'b': 1, 'c': 1 }, - { 'a': 4, 'b': 2, 'c': 1 }, - { 'a': 1, 'b': 3, 'c': 1 }, - { 'a': 3, 'b': 4, 'c': 5 }, - { 'a': 2, 'b': 3, 'c': 2 }, - { 'a': 1, 'b': 3, 'c': 1 }, - { 'a': 4, 'b': 3, 'c': 3 }, - { 'a': 2, 'b': 1, 'c': 1 } + raw_data = [ + {"date": "2020-06-06", "nb_connections": 5}, + {"date": "2020-06-05", "nb_connections": 25}, + {"date": "2020-06-04", "nb_connections": 1}, + {"date": "2020-06-03", "nb_connections": 1}, + {"date": "2020-06-02", "nb_connections": 4}, + {"date": "2020-06-01", "nb_connections": 1}, + {"date": "2020-05-31", "nb_connections": 0}, + {"date": "2020-05-30", "nb_connections": 2}, + {"date": "2020-05-29", "nb_connections": 1}, + {"date": "2020-05-28", "nb_connections": 0}, + {"date": "2020-05-27", "nb_connections": 1}, + {"date": "2020-05-26", "nb_connections": 0}, + {"date": "2020-05-25", "nb_connections": 0}, + {"date": "2020-05-25", "nb_connections": 0}, + {"date": "2020-05-24", "nb_connections": 3}, + {"date": "2020-05-23", "nb_connections": 2}, + {"date": "2020-05-22", "nb_connections": 2}, + {"date": "2020-05-22", "nb_connections": 2}, + {"date": "2020-05-21", "nb_connections": 2}, + {"date": "2020-05-20", "nb_connections": 1}, ] + + cols = ["date", "nb_connections"] + + stats_data = {c: [d[c] for d in raw_data] for c in cols} elif dataset == "2": - stats_data = [ - { 'f': 1, 'g': 1, 'h': 1 }, - { 'f': 4, 'g': 2, 'h': 1 }, - { 'f': 1, 'g': 3, 'h': 1 }, - { 'f': 3, 'g': 4, 'h': 5 }, - { 'f': 2, 'g': 3, 'h': 2 }, - { 'f': 1, 'g': 3, 'h': 1 }, - { 'f': 4, 'g': 3, 'h': 3 }, - { 'f': 2, 'g': 1, 'h': 1 } + raw_data = [ + { + "date": "2020-06-04", + "university": "337 - Universidad Autónoma de Aguascalientes", + "major": "GU", + "minor": "SR", + "exchange_semester": "A2019", + "nb_contributions": 1, + }, + { + "date": "2020-05-30", + "university": "26 - Cranfield University", + "major": "IM", + "minor": "MARS", + "exchange_semester": "A2019", + "nb_contributions": 1, + }, + { + "date": "2020-05-24", + "university": "343 - Universidad Pontificia Comillas", + "major": "TC", + "minor": "", + "exchange_semester": "A2018", + "nb_contributions": 1, + }, + { + "date": "2020-04-26", + "university": "344 - Technical University of Košice", + "major": "TC", + "minor": "", + "exchange_semester": "P2020", + "nb_contributions": 1, + }, + { + "date": "2020-04-10", + "university": "221 - École nationale supérieure des mines de Rabat", + "major": "IM", + "minor": "PIL", + "exchange_semester": "A2019", + "nb_contributions": 7, + }, + { + "date": "2020-03-24", + "university": "51 - University of Glasgow", + "major": "IM", + "minor": "CMI", + "exchange_semester": "A2019", + "nb_contributions": 6, + }, + { + "date": "2020-03-22", + "university": "53 - Technische Universität Braunschweig", + "major": "IM", + "minor": "CMI", + "exchange_semester": "A2019", + "nb_contributions": 4, + }, + { + "date": "2020-03-14", + "university": "53 - Technische Universität Braunschweig", + "major": "TC", + "minor": "", + "exchange_semester": "A2017", + "nb_contributions": 7, + }, + { + "date": "2020-03-14", + "university": "39 - Université du Québec à Chicoutimi", + "major": "IM", + "minor": "CMI", + "exchange_semester": "A2019", + "nb_contributions": 3, + }, + { + "date": "2020-03-11", + "university": "405 - National Chiao Tung University", + "major": "IM", + "minor": "CMI", + "exchange_semester": "A2019", + "nb_contributions": 6, + }, + { + "date": "2020-03-11", + "university": "200 - University of Science and Technology", + "major": "IM", + "minor": "MARS", + "exchange_semester": "A2019", + "nb_contributions": 3, + }, + { + "date": "2020-03-11", + "university": "170 - Silesian University of Technology", + "major": "IM", + "minor": "CMI", + "exchange_semester": "A2019", + "nb_contributions": 1, + }, + { + "date": "2020-03-11", + "university": "71 - Polytechnique Montréal", + "major": "IM", + "minor": "MARS", + "exchange_semester": "A2019", + "nb_contributions": 5, + }, + { + "date": "2020-03-10", + "university": "375 - Politechnika Wrocławska", + "major": "IM", + "minor": "CMI", + "exchange_semester": "A2019", + "nb_contributions": 5, + }, + { + "date": "2020-02-19", + "university": "376 - Warsaw University of Life Sciences", + "major": "GB", + "minor": "IAA", + "exchange_semester": "P2019", + "nb_contributions": 1, + }, + { + "date": "2020-02-12", + "university": "301 - Université de technologie sino-européenne de l'université de Shanghai", + "major": "HuTech", + "minor": "Spe-GI", + "exchange_semester": "P2019", + "nb_contributions": 1, + }, + { + "date": "2020-02-10", + "university": "57 - Technische Universität Graz", + "major": "TC", + "minor": "", + "exchange_semester": "P2018", + "nb_contributions": 9, + }, + { + "date": "2020-02-05", + "university": "71 - Polytechnique Montréal", + "major": "IM", + "minor": "SIM", + "exchange_semester": "A2019", + "nb_contributions": 2, + }, + { + "date": "2020-02-02", + "university": "51 - University of Glasgow", + "major": "IM", + "minor": "CMI", + "exchange_semester": "A2019", + "nb_contributions": 6, + }, + { + "date": "2020-01-31", + "university": "375 - Politechnika Wrocławska", + "major": "IM", + "minor": "CMI", + "exchange_semester": "A2019", + "nb_contributions": 1, + }, + ] + + cols = [ + "date", + "university", + "major", + "minor", + "exchange_semester", + "nb_contributions", ] + + stats_data = {c: [d[c] for d in raw_data] for c in cols} + else: return HttpResponseRedirect("/stats/?dataset=1") - return render(request, "stats.html", dict(stats_data=json.dumps(stats_data))) diff --git a/frontend/src/components/stats/RequestSQLHandler.jsx b/frontend/src/components/stats/RequestSQLHandler.jsx index d2cee6f5..6232166d 100644 --- a/frontend/src/components/stats/RequestSQLHandler.jsx +++ b/frontend/src/components/stats/RequestSQLHandler.jsx @@ -7,10 +7,15 @@ import TableFromData from "./Table"; import { makeStyles } from "@material-ui/styles"; import { Line } from "react-chartjs-2"; -const data = __StatsData; - -/* SELECT a, COUNT(*) AS c FROM ? GROUP BY a - SELECT a as x, COUNT(*) AS y FROM ? GROUP BY a */ +const cols = Object.keys(__StatsData); +const data = []; +for (let i = 0; i < __StatsData[cols[0]].length; i++) { + const elem = {}; + for (let col of cols) { + elem[col] = __StatsData[col][i]; + } + data.push(elem); +} const useStyles = makeStyles({}); @@ -31,15 +36,15 @@ function PlotResult({ result }) {
r.x), + labels: result.map((r) => r.x), datasets: [ { label: "My First dataset", //séparer par cat backgroundColor: "rgb(255, 99, 132)", borderColor: "rgb(255, 99, 132)", - data: result.map(r => r.y) - } - ] + data: result.map((r) => r.y), + }, + ], }} />
@@ -80,7 +85,12 @@ function SqlRequestResult({ result }) { ); } -const defaultRequest = "SELECT a as x, COUNT(*) AS y FROM ? GROUP BY a;"; +const defaultRequest = `SELECT + date AS x, + nb_connections AS y +FROM ? +ORDER BY x ASC; +`; function getRequestFromUrl() { const getParamsInUrl = new URL(document.location).searchParams; @@ -104,7 +114,7 @@ function getRequestFromUrl() { function setRequestInUrl(request) { const requestInfo = { request, - version: 1.0 + version: 1.0, }; const requestInfoAsString = btoa(JSON.stringify(requestInfo)); @@ -139,7 +149,7 @@ function SqlInterface() { rows={5} placeholder="Entrez la requête SQL" value={sqlRequest} - onChange={event => setSqlRequest(event.target.value)} + onChange={(event) => setSqlRequest(event.target.value)} /> {requestError !== "" && ( <> @@ -156,10 +166,10 @@ function SqlInterface() { setRequestError(""); setRequestResult([]); executeRequest(sqlRequest, data) - .then(res => { + .then((res) => { setRequestResult(res); }) - .catch(err => { + .catch((err) => { setRequestError(err); }); }} -- GitLab From 122f9e36fbb5db154a2f4507797190a883ca92bc Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Sat, 13 Jun 2020 14:45:43 +0200 Subject: [PATCH 06/13] feat(plot): the plot appears automatically if x and y are specified --- frontend/src/components/stats/Plot.jsx | 42 +++++++++++++++ .../components/stats/RequestSQLHandler.jsx | 51 +++---------------- 2 files changed, 48 insertions(+), 45 deletions(-) create mode 100644 frontend/src/components/stats/Plot.jsx diff --git a/frontend/src/components/stats/Plot.jsx b/frontend/src/components/stats/Plot.jsx new file mode 100644 index 00000000..99ebdb02 --- /dev/null +++ b/frontend/src/components/stats/Plot.jsx @@ -0,0 +1,42 @@ +import React, { useState, useEffect } from "react"; +import { makeStyles } from "@material-ui/styles"; +import { Line } from "react-chartjs-2"; + +const useStyles = makeStyles({}); + +function PlotResult({ result }) { + result.sort((a, b) => Object.values(a) > Object.values(b)); //demander à l'utilisateur de faire un order by + + if ( + !Object.keys(result[0]).includes("x") || + !Object.keys(result[0]).includes("y") + ) { + return ( +
+

+ Pour afficher les résultats sous forme graphique, vous devez préciser x + et y. +

+
+ ); + } + return ( +
+ r.x), + datasets: [ + { + label: "My First dataset", //séparer par cat + backgroundColor: "rgb(255, 99, 132)", + borderColor: "rgb(255, 99, 132)", + data: result.map(r => r.y) + } + ] + }} + /> +
+ ); +} + +export default PlotResult; diff --git a/frontend/src/components/stats/RequestSQLHandler.jsx b/frontend/src/components/stats/RequestSQLHandler.jsx index 6232166d..9cffd941 100644 --- a/frontend/src/components/stats/RequestSQLHandler.jsx +++ b/frontend/src/components/stats/RequestSQLHandler.jsx @@ -5,7 +5,7 @@ import TextField from "@material-ui/core/TextField"; import Button from "@material-ui/core/Button"; import TableFromData from "./Table"; import { makeStyles } from "@material-ui/styles"; -import { Line } from "react-chartjs-2"; +import PlotResult from "./Plot"; const cols = Object.keys(__StatsData); const data = []; @@ -23,41 +23,12 @@ function executeRequest(sqlRequest, data) { return alasql.promise(sqlRequest, [data]); } -function PlotResult({ result }) { - result.sort((a, b) => Object.values(a) > Object.values(b)); //demander à l'utilisateur de faire un order by - - if ( - !Object.keys(result[0]).includes("x") || - !Object.keys(result[0]).includes("y") - ) { - return
Erreur : Vous devez préciser x et y.
; - } - return ( -
- r.x), - datasets: [ - { - label: "My First dataset", //séparer par cat - backgroundColor: "rgb(255, 99, 132)", - borderColor: "rgb(255, 99, 132)", - data: result.map((r) => r.y), - }, - ], - }} - /> -
- ); -} - function SqlRequestResult({ result }) { - const [resultToPlot, setResultToPlot] = useState([]); - return (


+

- - {resultToPlot.length !== 0 && }
); } @@ -114,7 +75,7 @@ function getRequestFromUrl() { function setRequestInUrl(request) { const requestInfo = { request, - version: 1.0, + version: 1.0 }; const requestInfoAsString = btoa(JSON.stringify(requestInfo)); @@ -149,7 +110,7 @@ function SqlInterface() { rows={5} placeholder="Entrez la requête SQL" value={sqlRequest} - onChange={(event) => setSqlRequest(event.target.value)} + onChange={event => setSqlRequest(event.target.value)} /> {requestError !== "" && ( <> @@ -166,10 +127,10 @@ function SqlInterface() { setRequestError(""); setRequestResult([]); executeRequest(sqlRequest, data) - .then((res) => { + .then(res => { setRequestResult(res); }) - .catch((err) => { + .catch(err => { setRequestError(err); }); }} -- GitLab From 678fc7aa85a6880f9ca7a5e20884b74aa8aaa556 Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Sat, 13 Jun 2020 17:00:08 +0200 Subject: [PATCH 07/13] feat(backend): link database --- backend/base_app/views.py | 217 +++--------------- .../components/stats/RequestSQLHandler.jsx | 24 +- 2 files changed, 55 insertions(+), 186 deletions(-) diff --git a/backend/base_app/views.py b/backend/base_app/views.py index d608de89..25ce0ffd 100644 --- a/backend/base_app/views.py +++ b/backend/base_app/views.py @@ -1,8 +1,10 @@ +from datetime import datetime, timedelta import logging import json from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound from django.shortcuts import render +from django.utils.timezone import make_aware from webpack_loader.utils import get_files from backend_app.utils import clean_route @@ -16,6 +18,7 @@ from _cron_tasks import ( clear_and_clean_sessions, update_daily_stats, ) +from stats_app.models import DailyConnections, DailyExchangeContributionsInfo logger = logging.getLogger("django") @@ -52,195 +55,40 @@ def stats(request): """ dataset = request.GET.get("dataset") - if dataset == "1": + now = make_aware(datetime.now()) + now_minus_365_days = now - timedelta(days=365) + + if dataset == "daily_connections": + daily_connections = DailyConnections.objects.filter( + date__gte=now_minus_365_days, + date__lt=now, + ) raw_data = [ - {"date": "2020-06-06", "nb_connections": 5}, - {"date": "2020-06-05", "nb_connections": 25}, - {"date": "2020-06-04", "nb_connections": 1}, - {"date": "2020-06-03", "nb_connections": 1}, - {"date": "2020-06-02", "nb_connections": 4}, - {"date": "2020-06-01", "nb_connections": 1}, - {"date": "2020-05-31", "nb_connections": 0}, - {"date": "2020-05-30", "nb_connections": 2}, - {"date": "2020-05-29", "nb_connections": 1}, - {"date": "2020-05-28", "nb_connections": 0}, - {"date": "2020-05-27", "nb_connections": 1}, - {"date": "2020-05-26", "nb_connections": 0}, - {"date": "2020-05-25", "nb_connections": 0}, - {"date": "2020-05-25", "nb_connections": 0}, - {"date": "2020-05-24", "nb_connections": 3}, - {"date": "2020-05-23", "nb_connections": 2}, - {"date": "2020-05-22", "nb_connections": 2}, - {"date": "2020-05-22", "nb_connections": 2}, - {"date": "2020-05-21", "nb_connections": 2}, - {"date": "2020-05-20", "nb_connections": 1}, + { + "date": dc.date.strftime("%Y-%m-%d"), + "nb_connections": dc.nb_connections + } + for dc in daily_connections ] cols = ["date", "nb_connections"] - stats_data = {c: [d[c] for d in raw_data] for c in cols} - elif dataset == "2": + elif dataset == "daily_exchange_contributions": + daily_contributions = DailyExchangeContributionsInfos.objects.filter( + date__gte=now_minus_365_days, + date__lt=now, + ).prefetch_related("university") + raw_data = [ { - "date": "2020-06-04", - "university": "337 - Universidad Autónoma de Aguascalientes", - "major": "GU", - "minor": "SR", - "exchange_semester": "A2019", - "nb_contributions": 1, - }, - { - "date": "2020-05-30", - "university": "26 - Cranfield University", - "major": "IM", - "minor": "MARS", - "exchange_semester": "A2019", - "nb_contributions": 1, - }, - { - "date": "2020-05-24", - "university": "343 - Universidad Pontificia Comillas", - "major": "TC", - "minor": "", - "exchange_semester": "A2018", - "nb_contributions": 1, - }, - { - "date": "2020-04-26", - "university": "344 - Technical University of Košice", - "major": "TC", - "minor": "", - "exchange_semester": "P2020", - "nb_contributions": 1, - }, - { - "date": "2020-04-10", - "university": "221 - École nationale supérieure des mines de Rabat", - "major": "IM", - "minor": "PIL", - "exchange_semester": "A2019", - "nb_contributions": 7, - }, - { - "date": "2020-03-24", - "university": "51 - University of Glasgow", - "major": "IM", - "minor": "CMI", - "exchange_semester": "A2019", - "nb_contributions": 6, - }, - { - "date": "2020-03-22", - "university": "53 - Technische Universität Braunschweig", - "major": "IM", - "minor": "CMI", - "exchange_semester": "A2019", - "nb_contributions": 4, - }, - { - "date": "2020-03-14", - "university": "53 - Technische Universität Braunschweig", - "major": "TC", - "minor": "", - "exchange_semester": "A2017", - "nb_contributions": 7, - }, - { - "date": "2020-03-14", - "university": "39 - Université du Québec à Chicoutimi", - "major": "IM", - "minor": "CMI", - "exchange_semester": "A2019", - "nb_contributions": 3, - }, - { - "date": "2020-03-11", - "university": "405 - National Chiao Tung University", - "major": "IM", - "minor": "CMI", - "exchange_semester": "A2019", - "nb_contributions": 6, - }, - { - "date": "2020-03-11", - "university": "200 - University of Science and Technology", - "major": "IM", - "minor": "MARS", - "exchange_semester": "A2019", - "nb_contributions": 3, - }, - { - "date": "2020-03-11", - "university": "170 - Silesian University of Technology", - "major": "IM", - "minor": "CMI", - "exchange_semester": "A2019", - "nb_contributions": 1, - }, - { - "date": "2020-03-11", - "university": "71 - Polytechnique Montréal", - "major": "IM", - "minor": "MARS", - "exchange_semester": "A2019", - "nb_contributions": 5, - }, - { - "date": "2020-03-10", - "university": "375 - Politechnika Wrocławska", - "major": "IM", - "minor": "CMI", - "exchange_semester": "A2019", - "nb_contributions": 5, - }, - { - "date": "2020-02-19", - "university": "376 - Warsaw University of Life Sciences", - "major": "GB", - "minor": "IAA", - "exchange_semester": "P2019", - "nb_contributions": 1, - }, - { - "date": "2020-02-12", - "university": "301 - Université de technologie sino-européenne de l'université de Shanghai", - "major": "HuTech", - "minor": "Spe-GI", - "exchange_semester": "P2019", - "nb_contributions": 1, - }, - { - "date": "2020-02-10", - "university": "57 - Technische Universität Graz", - "major": "TC", - "minor": "", - "exchange_semester": "P2018", - "nb_contributions": 9, - }, - { - "date": "2020-02-05", - "university": "71 - Polytechnique Montréal", - "major": "IM", - "minor": "SIM", - "exchange_semester": "A2019", - "nb_contributions": 2, - }, - { - "date": "2020-02-02", - "university": "51 - University of Glasgow", - "major": "IM", - "minor": "CMI", - "exchange_semester": "A2019", - "nb_contributions": 6, - }, - { - "date": "2020-01-31", - "university": "375 - Politechnika Wrocławska", - "major": "IM", - "minor": "CMI", - "exchange_semester": "A2019", - "nb_contributions": 1, - }, + "date": dc.date.strftime("%Y-%m-%d"), + "university": f"{dc.university.pk} - {dc.university.name}", + "major": dc.major, + "minor": dc.minor, + "exchange_semester": dc.exchange_semester, + "nb_contributions": dc.nb_contributions, + } + for dc in daily_contributions ] cols = [ @@ -252,11 +100,10 @@ def stats(request): "nb_contributions", ] - stats_data = {c: [d[c] for d in raw_data] for c in cols} - else: - return HttpResponseRedirect("/stats/?dataset=1") + return HttpResponseRedirect("/stats/?dataset=daily_connections") + stats_data = {c: [d[c] for d in raw_data] for c in cols} return render(request, "stats.html", dict(stats_data=json.dumps(stats_data))) diff --git a/frontend/src/components/stats/RequestSQLHandler.jsx b/frontend/src/components/stats/RequestSQLHandler.jsx index 9cffd941..984a0d60 100644 --- a/frontend/src/components/stats/RequestSQLHandler.jsx +++ b/frontend/src/components/stats/RequestSQLHandler.jsx @@ -51,7 +51,7 @@ const defaultRequest = `SELECT nb_connections AS y FROM ? ORDER BY x ASC; -`; +`; //à changer selon le dataset function getRequestFromUrl() { const getParamsInUrl = new URL(document.location).searchParams; @@ -85,6 +85,28 @@ function setRequestInUrl(request) { window.history.replaceState(null, null, `?${getParamsInUrl.toString()}`); } +function getDatasetFromUrl() { + const getParamsInUrl = new URL(document.location).searchParams; + let datasetName = "daily_connections"; + + try { + datasetName = getParamsInUrl.has("dataset") + ? getParamsInUrl.get("dataset") + : "daily_connections"; + } catch (err) { + console.log(err); + } + + return datasetName; +} + +function setDatasetInUrl(datasetName) { + const getParamsInUrl = new URL(document.location).searchParams; + getParamsInUrl.set("dataset", datasetName); + + window.history.replaceState(null, null, `?${getParamsInUrl.toString()}`); +} + const requestFromUrl = getRequestFromUrl(); function SqlInterface() { -- GitLab From 55023dac9244896d32dd98462bef03da62b1352b Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Sat, 13 Jun 2020 18:04:53 +0200 Subject: [PATCH 08/13] feat(stats): fix datasets change --- backend/base_app/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/base_app/views.py b/backend/base_app/views.py index 25ce0ffd..50e6642c 100644 --- a/backend/base_app/views.py +++ b/backend/base_app/views.py @@ -74,7 +74,7 @@ def stats(request): cols = ["date", "nb_connections"] elif dataset == "daily_exchange_contributions": - daily_contributions = DailyExchangeContributionsInfos.objects.filter( + daily_contributions = DailyExchangeContributionsInfo.objects.filter( date__gte=now_minus_365_days, date__lt=now, ).prefetch_related("university") -- GitLab From 1f98822c768c62f787cd136d6bc51a0424b7d7ca Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Sat, 13 Jun 2020 18:07:11 +0200 Subject: [PATCH 09/13] feat(plot): the plot can be divided into different categories --- frontend/src/components/stats/Plot.jsx | 58 ++++++++++++++++++++------ 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/stats/Plot.jsx b/frontend/src/components/stats/Plot.jsx index 99ebdb02..16313ac3 100644 --- a/frontend/src/components/stats/Plot.jsx +++ b/frontend/src/components/stats/Plot.jsx @@ -4,9 +4,20 @@ import { Line } from "react-chartjs-2"; const useStyles = makeStyles({}); -function PlotResult({ result }) { - result.sort((a, b) => Object.values(a) > Object.values(b)); //demander à l'utilisateur de faire un order by +const colors = [ + "#1f77b4", + "#ff7f0e", + "#2ca02c", + "#d62728", + "#9467bd", + "#8c564b", + "#e377c2", + "#7f7f7f", + "#bcbd22", + "#17becf" +]; +function PlotResult({ result }) { if ( !Object.keys(result[0]).includes("x") || !Object.keys(result[0]).includes("y") @@ -14,25 +25,48 @@ function PlotResult({ result }) { return (


- Pour afficher les résultats sous forme graphique, vous devez préciser x - et y. + Pour afficher les résultats sous forme graphique, vous devez préciser + 'x' et 'y' dans le SELECT (pensez à utiliser un ORDER BY sur X). Vous + pouvez également préciser des catégories avec 'cat'.

); } + + if (!Object.keys(result[0]).includes("cat")) { + return ( +
+ r.x), + datasets: [ + { + label: "y = f(x)", + backgroundColor: "rgb(255, 99, 132)", + borderColor: "rgb(255, 99, 132)", + data: result.map(r => r.y) + } + ] + }} + /> +
+ ); + } + + const categories = [...new Set(result.map(el => el.cat))]; + const datasets = categories.map((c, index) => ({ + label: c, + fill: "disabled", + borderColor: colors[index % colors.length], + data: result.map(r => (r.cat === c ? r.y : 0)) + })); + return (
r.x), - datasets: [ - { - label: "My First dataset", //séparer par cat - backgroundColor: "rgb(255, 99, 132)", - borderColor: "rgb(255, 99, 132)", - data: result.map(r => r.y) - } - ] + datasets }} />
-- GitLab From ca752b2da29164748df60ffb4f3f653d541e874d Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Sat, 13 Jun 2020 18:11:22 +0200 Subject: [PATCH 10/13] feat(stats): add default requests --- .../components/stats/RequestSQLHandler.jsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/frontend/src/components/stats/RequestSQLHandler.jsx b/frontend/src/components/stats/RequestSQLHandler.jsx index 984a0d60..09db68a5 100644 --- a/frontend/src/components/stats/RequestSQLHandler.jsx +++ b/frontend/src/components/stats/RequestSQLHandler.jsx @@ -42,6 +42,9 @@ function SqlRequestResult({ result }) { > Exporter en CSV +

+

+ Pour partager votre recherche il vous suffit de copier l'url de la page !
); } @@ -52,6 +55,29 @@ const defaultRequest = `SELECT FROM ? ORDER BY x ASC; `; //à changer selon le dataset +// si plusieurs requetes par défaut proposées pour un meme dataset : les proposer avec des boutons +/* + SELECT + date AS x, + major AS cat, + sum(nb_contributions) AS y + FROM ? + GROUP BY date, major + ORDER BY date ASC; +*/ + +/** +SELECT + date AS x, + CASE WHEN + major = 'IM' OR major = 'GM' OR major = 'GSM' THEN 'IM' + ELSE 'AUTRES' + END AS cat, + sum(nb_contributions) AS y + FROM ? + GROUP BY date, major + ORDER BY date ASC; + */ function getRequestFromUrl() { const getParamsInUrl = new URL(document.location).searchParams; -- GitLab From fc9a9fd47150565604d053ebd6e05a2774707384 Mon Sep 17 00:00:00 2001 From: Estelle Veisemburger Date: Sat, 13 Jun 2020 18:11:55 +0200 Subject: [PATCH 11/13] feat(stats): add toolbar to pick dataset --- frontend/src/components/pages/PageStats.jsx | 32 +++++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/pages/PageStats.jsx b/frontend/src/components/pages/PageStats.jsx index e58cb4ef..213a3fc1 100644 --- a/frontend/src/components/pages/PageStats.jsx +++ b/frontend/src/components/pages/PageStats.jsx @@ -4,10 +4,20 @@ import Typography from "@material-ui/core/Typography"; import { makeStyles } from "@material-ui/styles"; import { withPaddedPaper } from "./shared"; import SqlInterface from "../stats/RequestSQLHandler"; +import AppBar from "@material-ui/core/AppBar"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import { appBarHeight, siteMaxWidth } from "../../config/sharedStyles"; -const useStyles = makeStyles({ - requestInterface: {} -}); +const useStyles = makeStyles(theme => ({ + requestInterface: {}, + tabBar: { + minHeight: theme.spacing(5), + [`@media (min-width:${siteMaxWidth()}px)`]: { + top: appBarHeight(theme) + } + } +})); /** * Component corresponding to the stats page of the site @@ -15,11 +25,27 @@ const useStyles = makeStyles({ function PageStats() { const classes = useStyles(); + //faire une fonction handleChange qui change le dataset dans l'url et recharge la page + return ( <> Exploration des statistiques d'utilisation du site REX-DRI + + + + + +
-- GitLab From 7db30bd59e04d87d4901a36d4b4ed3d0aced96a5 Mon Sep 17 00:00:00 2001 From: Florent Chehab Date: Sun, 14 Jun 2020 10:43:21 +0200 Subject: [PATCH 12/13] fix(front): stats plot * Enforce X --- frontend/src/components/stats/Plot.jsx | 60 ++++++++++++++++++-------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/stats/Plot.jsx b/frontend/src/components/stats/Plot.jsx index 16313ac3..60617630 100644 --- a/frontend/src/components/stats/Plot.jsx +++ b/frontend/src/components/stats/Plot.jsx @@ -1,8 +1,6 @@ -import React, { useState, useEffect } from "react"; -import { makeStyles } from "@material-ui/styles"; +import React from "react"; import { Line } from "react-chartjs-2"; - -const useStyles = makeStyles({}); +import PropTypes from "prop-types"; const colors = [ "#1f77b4", @@ -14,7 +12,7 @@ const colors = [ "#e377c2", "#7f7f7f", "#bcbd22", - "#17becf" + "#17becf", ]; function PlotResult({ result }) { @@ -24,53 +22,81 @@ function PlotResult({ result }) { ) { return (
-

+
Pour afficher les résultats sous forme graphique, vous devez préciser 'x' et 'y' dans le SELECT (pensez à utiliser un ORDER BY sur X). Vous pouvez également préciser des catégories avec 'cat'. -

+
); } if (!Object.keys(result[0]).includes("cat")) { + const mapping = new Map(); + result.forEach(({ x, y }) => { + mapping.set(x, y); + }); + + const X = [...mapping.keys()]; + X.sort(); + return (
r.x), + labels: X, datasets: [ { label: "y = f(x)", backgroundColor: "rgb(255, 99, 132)", borderColor: "rgb(255, 99, 132)", - data: result.map(r => r.y) - } - ] + data: X.map((x) => mapping.get(x)), + }, + ], }} />
); } - const categories = [...new Set(result.map(el => el.cat))]; - const datasets = categories.map((c, index) => ({ - label: c, + const categories = [...new Set(result.map((el) => el.cat))]; + + const mapping = new Map(); + result.forEach(({ x, y, cat }) => { + if (!mapping.has(x)) { + mapping.set(x, new Map()); + } + + mapping.get(x).set(cat, y); + }); + + const X = [...mapping.keys()]; + X.sort(); + + const datasets = categories.map((cat, index) => ({ + label: cat, fill: "disabled", borderColor: colors[index % colors.length], - data: result.map(r => (r.cat === c ? r.y : 0)) + data: X.map((x) => { + const val = mapping.get(x).get(cat); + return typeof val !== "undefined" ? val : 0; + }), })); return (
r.x), - datasets + labels: X, + datasets, }} />
); } +PlotResult.propTypes = { + result: PropTypes.array.isRequired, +}; + export default PlotResult; -- GitLab From 2ac7dcb01e27d7f2cff95c1f3f3020947b1f5ed1 Mon Sep 17 00:00:00 2001 From: Florent Chehab Date: Sun, 14 Jun 2020 10:43:51 +0200 Subject: [PATCH 13/13] style(front): stats --- frontend/src/components/pages/PageStats.jsx | 16 ++--- .../components/stats/RequestSQLHandler.jsx | 72 +++++++++++-------- frontend/src/components/stats/Table.jsx | 20 +++--- 3 files changed, 63 insertions(+), 45 deletions(-) diff --git a/frontend/src/components/pages/PageStats.jsx b/frontend/src/components/pages/PageStats.jsx index 213a3fc1..00f24246 100644 --- a/frontend/src/components/pages/PageStats.jsx +++ b/frontend/src/components/pages/PageStats.jsx @@ -1,22 +1,22 @@ -import React, { useState } from "react"; +import React from "react"; import { compose } from "recompose"; import Typography from "@material-ui/core/Typography"; import { makeStyles } from "@material-ui/styles"; -import { withPaddedPaper } from "./shared"; -import SqlInterface from "../stats/RequestSQLHandler"; import AppBar from "@material-ui/core/AppBar"; import Tabs from "@material-ui/core/Tabs"; import Tab from "@material-ui/core/Tab"; +import { withPaddedPaper } from "./shared"; +import SqlInterface from "../stats/RequestSQLHandler"; import { appBarHeight, siteMaxWidth } from "../../config/sharedStyles"; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ requestInterface: {}, tabBar: { minHeight: theme.spacing(5), [`@media (min-width:${siteMaxWidth()}px)`]: { - top: appBarHeight(theme) - } - } + top: appBarHeight(theme), + }, + }, })); /** @@ -25,7 +25,7 @@ const useStyles = makeStyles(theme => ({ function PageStats() { const classes = useStyles(); - //faire une fonction handleChange qui change le dataset dans l'url et recharge la page + // TODO faire une fonction handleChange qui change le dataset dans l'url et recharge la page return ( <> diff --git a/frontend/src/components/stats/RequestSQLHandler.jsx b/frontend/src/components/stats/RequestSQLHandler.jsx index 09db68a5..bf1ee40d 100644 --- a/frontend/src/components/stats/RequestSQLHandler.jsx +++ b/frontend/src/components/stats/RequestSQLHandler.jsx @@ -1,24 +1,29 @@ -import alasql from "alasql"; import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import alasql from "alasql"; import Typography from "@material-ui/core/Typography"; import TextField from "@material-ui/core/TextField"; import Button from "@material-ui/core/Button"; -import TableFromData from "./Table"; import { makeStyles } from "@material-ui/styles"; +import TableFromData from "./Table"; import PlotResult from "./Plot"; -const cols = Object.keys(__StatsData); +// eslint-disable-next-line no-undef +const DataFromBackend = __StatsData; +const cols = Object.keys(DataFromBackend); const data = []; -for (let i = 0; i < __StatsData[cols[0]].length; i++) { +// eslint-disable-next-line no-plusplus +for (let i = 0; i < DataFromBackend[cols[0]].length; i++) { const elem = {}; - for (let col of cols) { - elem[col] = __StatsData[col][i]; - } + cols.forEach((col) => { + elem[col] = DataFromBackend[col][i]; + }); data.push(elem); } const useStyles = makeStyles({}); +// eslint-disable-next-line no-shadow function executeRequest(sqlRequest, data) { return alasql.promise(sqlRequest, [data]); } @@ -26,13 +31,13 @@ function executeRequest(sqlRequest, data) { function SqlRequestResult({ result }) { return (
-

+
-

+
-

-

+
+
Pour partager votre recherche il vous suffit de copier l'url de la page !
); } +SqlRequestResult.propTypes = { + result: PropTypes.array.isRequired, +}; + const defaultRequest = `SELECT date AS x, nb_connections AS y FROM ? ORDER BY x ASC; -`; //à changer selon le dataset -// si plusieurs requetes par défaut proposées pour un meme dataset : les proposer avec des boutons +`; +// TODO à changer selon le dataset +// TODO si plusieurs requetes par défaut proposées pour un meme dataset : les proposer avec des boutons /* SELECT date AS x, @@ -67,14 +77,14 @@ ORDER BY x ASC; */ /** -SELECT - date AS x, - CASE WHEN - major = 'IM' OR major = 'GM' OR major = 'GSM' THEN 'IM' - ELSE 'AUTRES' - END AS cat, - sum(nb_contributions) AS y - FROM ? + SELECT + date AS x, + CASE WHEN + major = 'IM' OR major = 'GM' OR major = 'GSM' THEN 'IM' + ELSE 'AUTRES' + END AS cat, + sum(nb_contributions) AS y + FROM ? GROUP BY date, major ORDER BY date ASC; */ @@ -89,6 +99,7 @@ function getRequestFromUrl() { : ""; request = JSON.parse(atob(requestInfoString)).request; } catch (err) { + // eslint-disable-next-line no-console console.log(err); } @@ -101,7 +112,7 @@ function getRequestFromUrl() { function setRequestInUrl(request) { const requestInfo = { request, - version: 1.0 + version: 1.0, }; const requestInfoAsString = btoa(JSON.stringify(requestInfo)); @@ -111,6 +122,7 @@ function setRequestInUrl(request) { window.history.replaceState(null, null, `?${getParamsInUrl.toString()}`); } +// eslint-disable-next-line no-unused-vars function getDatasetFromUrl() { const getParamsInUrl = new URL(document.location).searchParams; let datasetName = "daily_connections"; @@ -120,12 +132,14 @@ function getDatasetFromUrl() { ? getParamsInUrl.get("dataset") : "daily_connections"; } catch (err) { + // eslint-disable-next-line no-console console.log(err); } return datasetName; } +// eslint-disable-next-line no-unused-vars function setDatasetInUrl(datasetName) { const getParamsInUrl = new URL(document.location).searchParams; getParamsInUrl.set("dataset", datasetName); @@ -158,27 +172,27 @@ function SqlInterface() { rows={5} placeholder="Entrez la requête SQL" value={sqlRequest} - onChange={event => setSqlRequest(event.target.value)} + onChange={(event) => setSqlRequest(event.target.value)} /> {requestError !== "" && ( <> -

+
{requestError.toString()} -

+
)}