├── server
├── schema.js
├── resolvers
│ ├── Mutation.js
│ ├── Query.js
│ └── Subscription.js
├── model.js
├── traqlRouter.js
├── controllers
│ ├── authTokenController.js
│ ├── AqlDatabaseController.js
│ └── traqlController.js
├── router.js
├── server.js
└── helperFuncs.js
├── README.md
├── src
├── components
│ ├── HeaderBar.jsx
│ ├── BottomRow
│ │ ├── Log.jsx
│ │ ├── BottomRow.jsx
│ │ ├── AqlRowHeader.jsx
│ │ ├── LogHeader.jsx
│ │ ├── AqlRow.jsx
│ │ ├── LogContainer.jsx
│ │ ├── AqlContainer.jsx
│ │ └── MutationRow.jsx
│ ├── MiddleRow
│ │ ├── Map.jsx
│ │ ├── MapContainer.jsx
│ │ ├── NodeContainer.jsx
│ │ ├── PieChartContainer.jsx
│ │ ├── MiddleRow.jsx
│ │ ├── ErrorLogContainer.jsx
│ │ ├── Node.jsx
│ │ └── PieChart.jsx
│ ├── Footer.jsx
│ ├── TopRow
│ │ ├── TopRow.jsx
│ │ ├── PieChartContainer.jsx
│ │ ├── LineChartContainer.jsx
│ │ ├── PieChart.jsx
│ │ ├── LineChart.jsx
│ │ └── LineChart-old.jsx
│ ├── LandingPage
│ │ ├── navbar.jsx
│ │ └── landingPage.jsx
│ ├── useResizeObserver.js
│ ├── DashboardContainer.jsx
│ └── NavBar.jsx
├── index.js
└── App.jsx
├── .gitignore
├── public
├── littleaql.png
├── aql-icon
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── mstile-150x150.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── browserconfig.xml
│ └── site.webmanifest
├── scss
│ ├── application.scss
│ ├── variables.scss
│ ├── landingPage.scss
│ ├── globals.scss
│ └── dashboard.scss
└── index.html
├── LICENSE
├── webpack.config.js
├── package.json
└── dummydata.js
/server/schema.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # aql-monitor
--------------------------------------------------------------------------------
/server/resolvers/Mutation.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/resolvers/Query.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/HeaderBar.jsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
--------------------------------------------------------------------------------
/server/resolvers/Subscription.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/littleaql.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpinchak/aql-monitor/HEAD/public/littleaql.png
--------------------------------------------------------------------------------
/public/aql-icon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpinchak/aql-monitor/HEAD/public/aql-icon/favicon.ico
--------------------------------------------------------------------------------
/public/aql-icon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpinchak/aql-monitor/HEAD/public/aql-icon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/aql-icon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpinchak/aql-monitor/HEAD/public/aql-icon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/scss/application.scss:
--------------------------------------------------------------------------------
1 | @import './globals.scss';
2 | @import './variables.scss';
3 | @import './dashboard.scss';
4 |
--------------------------------------------------------------------------------
/public/aql-icon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpinchak/aql-monitor/HEAD/public/aql-icon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/aql-icon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpinchak/aql-monitor/HEAD/public/aql-icon/mstile-150x150.png
--------------------------------------------------------------------------------
/public/aql-icon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpinchak/aql-monitor/HEAD/public/aql-icon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/aql-icon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpinchak/aql-monitor/HEAD/public/aql-icon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/scss/variables.scss:
--------------------------------------------------------------------------------
1 | $primary-color: white;
2 | $secondary-color: #0fa0da;
3 | $tertiary-color: #87c5dd;
4 | $hover-color: rgb(10, 128, 175);
5 | $background-color: #1b2035;
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import App from './App.jsx';
4 | import '../public/scss/application.scss';
5 |
6 | render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/src/components/BottomRow/Log.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from 'react';
2 |
3 | /**
4 | * Component that renders a Log Entry
5 | */
6 |
7 | function Log() {
8 | return
;
9 | }
10 |
11 | export default Log;
12 |
--------------------------------------------------------------------------------
/src/components/MiddleRow/Map.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from 'react';
2 |
3 | /**
4 | * Component that renders a Map
5 | */
6 |
7 | function Map() {
8 | return Map
;
9 | }
10 |
11 | export default Map;
12 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Footer() {
4 | return (
5 |
11 | );
12 | }
13 |
14 | export default Footer;
15 |
--------------------------------------------------------------------------------
/public/aql-icon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/server/model.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 |
3 | const { Pool } = require('pg');
4 | const pg_url = process.env.DATABASE_URL;
5 | const pool = new Pool({ connectionString: pg_url });
6 |
7 | module.exports = {
8 | query: (text, params, callback) => {
9 | return pool.query(text, params, callback);
10 | },
11 | };
--------------------------------------------------------------------------------
/server/traqlRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const db = require('./model');
3 | const traqlRouter = express.Router();
4 | const traqlController = require('./controllers/traqlController');
5 |
6 | traqlRouter.post('/', traqlController.addAqlsToTraql, (req, res) => {
7 | res.sendStatus(200);
8 | });
9 |
10 | module.exports = traqlRouter;
--------------------------------------------------------------------------------
/src/components/BottomRow/BottomRow.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | // IMPORT COMPONENTS
3 | import LogContainer from './LogContainer.jsx';
4 |
5 | function BottomRow(props) {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default BottomRow;
14 |
--------------------------------------------------------------------------------
/src/components/MiddleRow/MapContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react';
2 | import Map from './Map.jsx';
3 |
4 | function MapContainer(props) {
5 | return (
6 |
7 |
Location Heat Map
8 |
9 |
10 | );
11 | }
12 |
13 | export default MapContainer;
14 |
--------------------------------------------------------------------------------
/src/components/MiddleRow/NodeContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react';
2 | import Node from './Node.jsx';
3 |
4 | function NodeContainer(props) {
5 | return (
6 |
7 |
Resolver Latency
8 |
9 |
10 | );
11 | }
12 |
13 | export default NodeContainer;
14 |
--------------------------------------------------------------------------------
/src/components/MiddleRow/PieChartContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react';
2 | import PieChart from './PieChart.jsx';
3 |
4 | function PieChartContainer(props) {
5 | return (
6 |
7 |
Resolver Frequency
8 |
9 |
10 | );
11 | }
12 |
13 | export default PieChartContainer;
14 |
--------------------------------------------------------------------------------
/src/components/TopRow/TopRow.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | // IMPORT COMPONENTS
3 | import LineChartContainer from './LineChartContainer.jsx';
4 |
5 | function TopRow(props) {
6 | return (
7 |
8 |
12 |
13 | );
14 | }
15 |
16 | export default TopRow;
17 |
--------------------------------------------------------------------------------
/server/controllers/authTokenController.js:
--------------------------------------------------------------------------------
1 | const authToken = {};
2 | const db = require('../model.js');
3 |
4 | authToken.getToken = (req, res, next) => {
5 | const githubId = [req.user.id];
6 | const tokenQuery = `SELECT user_token FROM users WHERE github_id = $1;`
7 | db.query(tokenQuery, githubId)
8 | .then((token) => {
9 | res.locals.token = token.rows[0].user_token;
10 | return next()
11 | })
12 | };
13 |
14 | module.exports = authToken;
--------------------------------------------------------------------------------
/server/router.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const Router = express.Router();
3 | const aqlDatabaseController = require('./controllers/AqlDatabaseController');
4 |
5 | Router.get('/', aqlDatabaseController.getAqls, (req, res) => {
6 | res.status(200).json(res.locals.data);
7 | });
8 |
9 | Router.get('/user', aqlDatabaseController.getUserData, (req, res) => {
10 | res.status(200).send(res.locals.userData);
11 | });
12 |
13 | module.exports = Router;
14 |
--------------------------------------------------------------------------------
/src/components/BottomRow/AqlRowHeader.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from 'react';
2 |
3 | /**
4 | * Component that renders a Log Entry
5 | */
6 |
7 | function AqlRowHeader() {
8 | return (
9 |
10 |
Time
11 |
ID
12 |
Resolver
13 |
Subscribers
14 |
Latency
15 |
16 | );
17 | }
18 |
19 | export default AqlRowHeader;
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 | Aql
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/aql-icon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/BottomRow/LogHeader.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from 'react';
2 |
3 | /**
4 | * Component that renders a Log Entry
5 | */
6 |
7 | function LogHeader() {
8 | return (
9 |
18 | );
19 | }
20 |
21 | export default LogHeader;
22 |
--------------------------------------------------------------------------------
/src/components/MiddleRow/MiddleRow.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | // IMPORT COMPONENTS
3 | import PieChartContainer from './PieChartContainer.jsx';
4 | import ErrorLogContainer from './ErrorLogContainer.jsx';
5 |
6 | function MiddleRow(props) {
7 | return (
8 |
15 | );
16 | }
17 |
18 | export default MiddleRow;
19 |
--------------------------------------------------------------------------------
/src/components/TopRow/PieChartContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react';
2 | import PieChart from './PieChart.jsx';
3 |
4 | function PieChartContainer(props) {
5 | return (
6 |
7 |
Resolver Frequency
8 |
15 |
16 | );
17 | }
18 |
19 | export default PieChartContainer;
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/BottomRow/AqlRow.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function AqlRow(props) {
4 | const localDateTime = new Date(parseInt(props.data.subscriber_received_time));
5 | return (
6 |
7 |
8 | {localDateTime.toLocaleTimeString()} {localDateTime.getMilliseconds()}{' '}
9 | ms
10 |
11 |
{props.data.id}
12 |
{props.data.resolver}
13 |
{props.data.expected_subscribers}
14 |
{props.data.latency}
15 |
16 | );
17 | }
18 |
19 | export default AqlRow;
20 |
--------------------------------------------------------------------------------
/src/components/BottomRow/LogContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react';
2 | import LogHeader from './LogHeader.jsx';
3 | import MutationRow from './MutationRow.jsx';
4 |
5 | function LogContainer(props) {
6 | // loop through mutations
7 | const mutationRows = [];
8 | for (let el of props.data.mutations) {
9 | mutationRows.push();
10 | }
11 |
12 | return (
13 |
14 |
15 |
{mutationRows}
16 |
17 | );
18 | }
19 |
20 | export default LogContainer;
21 |
--------------------------------------------------------------------------------
/src/components/TopRow/LineChartContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react';
2 | import LineChart from './LineChart.jsx';
3 |
4 | function LineChartContainer(props) {
5 |
6 | return (
7 |
8 |
Round-Trip
9 |
10 |
11 |
12 | {/**/}
16 |
17 |
18 | );
19 | }
20 |
21 | export default LineChartContainer;
22 |
--------------------------------------------------------------------------------
/src/components/BottomRow/AqlContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AqlRow from './AqlRow.jsx';
3 | import AqlRowHeader from './AqlRowHeader.jsx';
4 | import { animated, useSpring } from 'react-spring';
5 |
6 | function AqlContainer(props) {
7 | const aqlContainerStyle = useSpring({
8 | height: props.expanded ? '100px' : '0vh',
9 | opacity: props.expanded ? 1 : 0,
10 | });
11 |
12 | const aqlRows = [];
13 | for (let el of props.data) {
14 | aqlRows.push();
15 | }
16 | return (
17 |
18 |
19 | {aqlRows}
20 |
21 | );
22 | }
23 |
24 | export default AqlContainer;
25 |
--------------------------------------------------------------------------------
/src/components/LandingPage/navbar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import { BrowserRouter as Router, Link, Switch, NavLink } from 'react-router-dom';
3 |
4 | // const getUserData = async () => {
5 | // const response = await fetch('/githublogin');
6 | // const data = await response.json();
7 | // setUserData(data);
8 | // };
9 |
10 | function Navbar () {
11 | return(
12 |
13 |
14 | - Home
15 | - Team
16 | {/*
17 |
18 |
21 |
22 | */}
23 |
24 |
25 | )
26 | };
27 |
28 | export default Navbar;
--------------------------------------------------------------------------------
/src/components/useResizeObserver.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import ResizeObserver from 'resize-observer-polyfill';
3 |
4 | /**
5 | * Hook, that returns the current dimensions of an HTML element.
6 | * Doesn't play well with SVG.
7 | */
8 |
9 | const useResizeObserver = (ref) => {
10 | const [dimensions, setDimensions] = useState(null);
11 | useEffect(() => {
12 | const observeTarget = ref.current;
13 | const resizeObserver = new ResizeObserver((entries) => {
14 | entries.forEach((entry) => {
15 | setDimensions(entry.contentRect);
16 | });
17 | });
18 | resizeObserver.observe(observeTarget);
19 | return () => {
20 | resizeObserver.unobserve(observeTarget);
21 | };
22 | }, [ref]);
23 | return dimensions;
24 | };
25 |
26 | export default useResizeObserver;
27 |
--------------------------------------------------------------------------------
/src/components/MiddleRow/ErrorLogContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MutationRow from '../BottomRow/MutationRow.jsx';
3 | import LogHeader from '../BottomRow/LogHeader.jsx';
4 |
5 | function ErrorLogContainer(props) {
6 | if(props.length) {
7 | console.log(props)
8 | // loop through mutations
9 | const mutationRows = [];
10 | for (let el of props.data.errors) {
11 | mutationRows.push();
12 | };
13 | }
14 |
15 | return (
16 |
17 |
18 |
{props.length && mutationRows.length ? mutationRows : "no errors to display"}
19 |
No data to display
20 |
21 |
22 | );
23 | }
24 |
25 | export default ErrorLogContainer;
26 |
--------------------------------------------------------------------------------
/src/components/LandingPage/landingPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Router, Link, Switch, NavLink } from 'react-router-dom';
3 | import Navbar from './navbar.jsx';
4 | import aqlLogo from '../../../public/littleaql.png';
5 |
6 | function LandingPage() {
7 | return (
8 |
9 |
10 |
11 |
Welcome to Aql
12 |
13 |

14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 |
29 | export default LandingPage;
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { BrowserRouter as Router, Route, Link, Switch, NavLink } from 'react-router-dom';
3 |
4 | // COMPONENT IMPORTS
5 | import NavBar from './components/NavBar.jsx';
6 | import DashboardContainer from './components/DashboardContainer.jsx';
7 | import Footer from './components/Footer.jsx';
8 | // SCSS
9 | import '../public/scss/application.scss';
10 | import '../public/scss/landingPage.scss';
11 | //import landing page
12 | import LandingPage from '../src/components/LandingPage/landingPage.jsx';
13 | import Cookies from 'js-cookie';
14 |
15 | const userTokenCookie = Cookies.get('userToken');
16 |
17 | function App() {
18 | const [userToken, setUserToken] = useState(userTokenCookie);
19 | return (
20 | userToken ?
21 |
22 |
23 |
24 |
25 | :
26 |
27 |
28 |
29 | )
30 | };
31 |
32 | export default App;
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 AqlOrg
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: process.env.NODE_ENV,
5 | entry: './src/index.js',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, './build'),
9 | publicPath: '/build/',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.js$|jsx/,
15 | use: {
16 | loader: 'babel-loader',
17 | options: {
18 | presets: ['@babel/preset-env', '@babel/preset-react'],
19 | },
20 | },
21 | exclude: /node_modules/,
22 | },
23 | {
24 | test: /\.s?css$/,
25 | use: ['style-loader', 'css-loader', 'sass-loader'],
26 | },
27 | {
28 | test: /\.(png|jpe?g|gif)$/i,
29 | use: {
30 | loader: 'file-loader',
31 | options: {
32 | name: '[path][name].[ext]',
33 | },
34 | },
35 | },
36 | ],
37 | },
38 | resolve: {
39 | extensions: ['.js', '.jsx'],
40 | },
41 | devServer: {
42 | contentBase: path.resolve(__dirname, './public'),
43 | port: 8080,
44 | proxy: {
45 | '/': 'http://localhost:3000',
46 | },
47 | publicPath: '/build/',
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/DashboardContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | // IMPORT COMPONENTS
3 | import TopRow from './TopRow/TopRow.jsx';
4 | import MiddleRow from './MiddleRow/MiddleRow.jsx';
5 | import BottomRow from './BottomRow/BottomRow.jsx';
6 | import Cookies from 'js-cookie';
7 |
8 | const userTokenCookie = Cookies.get('userToken');
9 |
10 | function DashboardContainer(props) {
11 |
12 | const [ready, setReady] = useState(false);
13 | const [aqlData, setAqlData] = useState({});
14 | const [userToken, setUserToken] = useState(userTokenCookie);
15 | const [userInfo, setUserInfo] = useState({})
16 |
17 | // fetching user data
18 | useEffect(() => {
19 | fetch('/api/user')
20 | // .then(res => res.json())
21 | .then(res => setUserInfo(res))
22 | .catch(err => console.log(err));
23 | }, [userToken]);
24 |
25 | // fetching user analytics
26 | useEffect(() => {
27 | fetch('/api')
28 | .then(res => res.json())
29 | .then(data => setAqlData(data))
30 | .then(() => setReady(true))
31 | .catch(err => console.log(err));
32 | }, []);
33 |
34 | return (
35 | ready && (
36 |
37 |
40 |
43 |
44 |
45 | )
46 | );
47 | }
48 |
49 |
50 | export default DashboardContainer;
51 |
--------------------------------------------------------------------------------
/src/components/NavBar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import Cookies from 'js-cookie';
3 |
4 | function logout() {
5 | Cookies.remove('userToken', { path: ''})
6 | window.location.reload(false)
7 | }
8 |
9 | function NavBar() {
10 | const dropdownRef= useRef(null);
11 | const [isActive, setIsActive] = useState(false);
12 | const handleDropdown = () => setIsActive(!isActive);
13 |
14 | // collapse dropdown menu when clicking away
15 | useEffect(() => {
16 | const pageClickEvent = (e) => {
17 | // If the active element exists and is clicked outside of
18 | if (dropdownRef.current !== null && !dropdownRef.current.contains(e.target)) {
19 | setIsActive(!isActive);
20 | }
21 | };
22 |
23 | // if menu is active, then listen for clicks
24 | if(isActive) {
25 | window.addEventListener('click', pageClickEvent);
26 | }
27 |
28 | return () => {
29 | window.removeEventListener('click', pageClickEvent);
30 | }
31 |
32 | }, [isActive]);
33 |
34 | return (
35 |
36 |
Aql
37 |
38 |
45 |
46 | );
47 | }
48 |
49 | export default NavBar;
50 |
--------------------------------------------------------------------------------
/public/scss/landingPage.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 |
3 | #nav {
4 | width: 100%;
5 | float: left;
6 | margin: 0 0 3em 0;
7 | padding: 0;
8 | list-style: none;
9 | font-family: Arial, Helvetica, sans-serif;
10 |
11 | li {
12 | float: left;
13 |
14 | a {
15 | display: block;
16 | padding: 15px 15px;
17 | text-decoration: none;
18 | font-size: 14px;
19 | color: white;
20 | }
21 | }
22 | }
23 |
24 | #landingdiv {
25 | position: absolute;
26 | width: 30vw;
27 | height: 30vw;
28 | background-color: none;
29 | display: flex;
30 | flex-direction: column;
31 | top: 40%;
32 | left: 50%;
33 | margin-top: -15vw;
34 | margin-left: -15vw;
35 | justify-content: space-evenly;
36 | align-items: center;
37 | }
38 |
39 | #welcome {
40 | font-family: Arial, Helvetica, sans-serif;
41 | color: $secondary-color;
42 | font-size: 2vw;
43 | }
44 |
45 | #aqlholder {
46 | margin-top: 5px;
47 | display: flex;
48 | background-color: $primary-color;
49 | border: none;
50 | border-radius: 25%;
51 | width: 20vw;
52 | height: 20vw;
53 | justify-content: center;
54 | align-items: center;
55 | }
56 |
57 | #landingAql {
58 | position: absolute;
59 | width: 50%;
60 | }
61 |
62 | .buttonholder {
63 | width: 105px;
64 | }
65 |
66 | #loginbutton {
67 | background-color: $secondary-color;
68 | color: $primary-color;
69 | margin: 1em 1em 0.5em 0;
70 | border-radius: 5px;
71 | padding: 5px 15px 5px 15px;
72 | border: none;
73 | outline: none;
74 | font-size: 13px;
75 | transition: 0.5s;
76 | }
77 |
78 | #loginbutton:hover {
79 | background-color: $hover-color;
80 | }
--------------------------------------------------------------------------------
/src/components/BottomRow/MutationRow.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import AqlContainer from './AqlContainer.jsx';
3 | import { CgAdd, CgCloseO } from 'react-icons/cg';
4 | import { useSpring, animated, interpolate } from 'react-spring';
5 |
6 | function MutationRow(props) {
7 | const [expanded, setExpanded] = useState(false);
8 | const handleClick = () => {
9 | setExpanded(!expanded);
10 | };
11 | const dateTime = new Date(parseInt(props.data.dateTime));
12 |
13 | const spring = useSpring({
14 | x: expanded ? 45 : 0,
15 | y: expanded ? 1 : 0,
16 | config: { tension: 800 },
17 | });
18 |
19 | const AnimatedIcon = animated(CgAdd);
20 |
21 | return (
22 |
31 |
32 |
{dateTime.toLocaleDateString()}
33 |
{dateTime.toLocaleTimeString()}
34 |
{props.data.mutationId}
35 |
{props.data.resolver}
36 |
{props.data.expectedAqls}
37 |
{props.data.avgLatency}
38 |
49 |
50 |
51 |
52 | );
53 | }
54 |
55 | export default MutationRow;
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aql-monitor",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "./src/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node ./server/server.js",
9 | "build": "cross-env NODE_ENV=producton webpack",
10 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --open\" \"nodemon ./server/server.js\""
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/AqlOrg/aql-monitor.git"
15 | },
16 | "author": "",
17 | "license": "ISC",
18 | "bugs": {
19 | "url": "https://github.com/AqlOrg/aql-monitor/issues"
20 | },
21 | "homepage": "https://github.com/AqlOrg/aql-monitor#readme",
22 | "dependencies": {
23 | "chart.js": "^2.9.3",
24 | "axios": "^0.20.0",
25 | "d3": "^6.2.0",
26 | "express": "^4.17.1",
27 | "graphql": "^15.3.0",
28 | "js-cookie": "^2.2.1",
29 | "passport": "^0.4.1",
30 | "passport-github": "^1.1.0",
31 | "pg": "^8.4.0",
32 | "react": "^16.13.1",
33 | "react-chartjs-2": "^2.10.0",
34 | "react-dom": "^16.13.1",
35 | "react-icons": "^3.11.0",
36 | "react-spring": "^8.0.27",
37 | "react-router-dom": "^5.2.0",
38 | "resize-observer-polyfill": "^1.5.1",
39 | "sass": "^1.26.11",
40 | "uuid": "^8.3.1"
41 | },
42 | "devDependencies": {
43 | "@babel/core": "^7.11.6",
44 | "@babel/preset-env": "^7.11.5",
45 | "@babel/preset-react": "^7.10.4",
46 | "babel-core": "^6.26.3",
47 | "babel-loader": "^8.1.0",
48 | "babel-polyfill": "^6.26.0",
49 | "babel-preset-es2015": "^6.24.1",
50 | "babel-preset-stage-0": "^6.24.1",
51 | "concurrently": "^5.3.0",
52 | "cors": "^2.8.5",
53 | "cross-env": "^7.0.2",
54 | "css-loader": "^4.3.0",
55 | "dotenv": "^8.2.0",
56 | "file-loader": "^6.1.1",
57 | "nodemon": "^2.0.4",
58 | "sass-loader": "^10.0.2",
59 | "style-loader": "^1.3.0",
60 | "webpack": "^4.44.2",
61 | "webpack-cli": "^3.3.12",
62 | "webpack-dev-server": "^3.11.0"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/server/controllers/AqlDatabaseController.js:
--------------------------------------------------------------------------------
1 | const aqlDatabaseController = {};
2 | const db = require('../model');
3 | const {
4 | resolverStats,
5 | subscriptionHistory,
6 | mutations,
7 | } = require('../helperFuncs');
8 | const { query } = require('express');
9 |
10 | //Analytics from Aql table
11 | aqlDatabaseController.getAqls = (req, res, next) => {
12 | const userToken = req.headers.cookie;
13 | //use the mainUserToken to query the database
14 | const mainUserToken = [...userToken].splice(10,[...userToken].length - 1).join('');
15 |
16 | const tokenQuery = [mainUserToken];
17 | const queryString = `SELECT * FROM aql WHERE user_token = $1;`;
18 |
19 | db.query(queryString, tokenQuery, (err, data) => {
20 | // If error, console.log
21 | if (err) console.log('ERROR: ', err);
22 | // create a dataObj to add shaped data from calling helperFuncs
23 | const dataObj = {};
24 | if(data.rows.length > 0) {
25 | dataObj.resolverStats = resolverStats(data);
26 | dataObj.subscriptionHistory = subscriptionHistory(data);
27 | // destructure mutations and errors from E.R. of mutations(data)
28 | const [mutationsArr, errors] = mutations(data);
29 | // add mutations prop with mutations as val
30 | // add errors prop
31 | dataObj.mutations = mutationsArr;
32 | dataObj.errors = errors;
33 | res.locals.data = dataObj;
34 | }
35 | return next();
36 | });
37 | }
38 |
39 | //Querying user data from the database by user_token
40 | aqlDatabaseController.getUserData = (req, res, next) => {
41 | const userToken = req.headers.cookie;
42 | const mainUserToken = [...userToken].splice(10,[...userToken].length - 1).join('');
43 |
44 | const queryString = `
45 | SELECT
46 | username,
47 | github_id,
48 | avatar_url
49 | FROM users
50 | WHERE user_token = $1;
51 | `
52 | const tokenQuery = [mainUserToken];
53 | db.query(queryString, tokenQuery)
54 | .then(userData => {
55 | res.locals.userData = userData.rows[0];
56 | return next();
57 | })
58 | }
59 |
60 | module.exports = aqlDatabaseController;
61 |
--------------------------------------------------------------------------------
/src/components/TopRow/PieChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import * as d3 from 'd3';
3 |
4 | const PieChart = (props) => {
5 |
6 | let uselessData = [{name:'fds', value: 100}, {name:'fds', value: 150}];
7 | const moreuseless = [1,2,3,4]
8 | //let dumData = props.dummyData.resolverStats.map(elt => elt);
9 | //console.log(dumData);
10 |
11 | const ref = useRef();
12 |
13 | const createPie = d3
14 | .pie()
15 | .value((d) => d.value)
16 | .sort(null);
17 | const createArc = d3
18 | .arc()
19 | .innerRadius(props.innerRadius)
20 | .outerRadius(props.outerRadius);
21 | const colors = d3.interpolateBlues;
22 |
23 | // useEffect(() => {
24 |
25 | // const data = createPie(dumData);
26 |
27 | // const group = d3.select(ref.current);
28 | // const groupWithData = group.selectAll('g.arc').data(data);
29 |
30 | // groupWithData
31 | // .enter()
32 | // .append('g')
33 | // .attr('class', 'arc');
34 |
35 | // const path = groupWithData
36 | // .append('path')
37 | // .merge(groupWithData.select('path.arc'));
38 |
39 | // path
40 | // .attr('class', 'arc')
41 | // .attr('d', createArc)
42 | // .attr('fill', (d, i) => colors(i/(props.resolverStats.length)));
43 |
44 | // const text = groupWithData
45 | // .append('text')
46 | // .merge(groupWithData.select('text'));
47 |
48 | // text
49 | // .attr('text-anchor', 'middle')
50 | // .attr('alignment-baseline', 'middle')
51 | // .attr('transform', (d) => `translate(${createArc.centroid(d)})`)
52 | // .style('fill', 'black')
53 | // .style('font-size', '10')
54 | // .style('font-family', 'Arial')
55 | // .style('text-align', 'middle')
56 | // //.text((d) => d.data.name);
57 | // }, []);
58 |
59 | return (
60 | props.length?
61 |
67 | :
68 | No data to display
69 | );
70 | };
71 |
72 | export default PieChart;
73 |
--------------------------------------------------------------------------------
/public/scss/globals.scss:
--------------------------------------------------------------------------------
1 | @import './variables';
2 |
3 | html,
4 | body {
5 | background-color: $background-color;
6 | }
7 |
8 | // NAVBAR STYLES: LOGO AND USER BUTTON
9 | #navbar {
10 | display: flex;
11 | justify-content: space-between;
12 | align-items: center;
13 | position: fixed;
14 | top: 0px;
15 | width: 100vw;
16 | padding-top: 15px;
17 | padding-bottom: 15px;
18 | // border-bottom: 1px solid $secondary-color;
19 |
20 | font-family: Georgia, 'Times New Roman', Times, serif;
21 |
22 | #logo {
23 | font-size: 40px;
24 | color: $primary-color;
25 | margin-left: 25px;
26 | }
27 |
28 | #logoutbutton {
29 | position: absolute;
30 | transform: translate(-45px, -10px);
31 | color: $primary-color;
32 | background-color: $background-color;
33 | border: none;
34 | border: none;
35 | outline: none;
36 | }
37 |
38 | #userbutton {
39 | border-radius: 50%;
40 | background-color: $secondary-color;
41 | border: none;
42 | height: 30px;
43 | width: 30px;
44 | margin-right: 25px;
45 | border: none;
46 | outline: none;
47 | z-index: 10;
48 | transition: 0.5s;
49 | }
50 |
51 | #userbutton:hover {
52 | background-color: $hover-color;
53 | }
54 |
55 | .dropdown-menu {
56 | background-color: $background-color;
57 | border: 1px solid $secondary-color;
58 | border-radius: 5px;
59 | position: absolute;
60 | top: 60px;
61 | right: 25px;
62 | width: 85px;
63 | height: 30px;
64 | opacity: 0;
65 | visibility: hidden;
66 | transform: translateY(-20px);
67 | display: flex;
68 | flex-direction: column;
69 | vertical-align: middle;
70 | align-items: center;
71 | transition: 0.5s ease;
72 | }
73 |
74 | .dropdown-menu.active {
75 | opacity: 1;
76 | visibility: visible;
77 | transform: translateY(0);
78 | top: 60px;
79 | }
80 |
81 | .dropdown-menu:hover {
82 | border: 1px solid $tertiary-color;
83 | }
84 |
85 | .dropdown-menu ul {
86 | list-style: none;
87 | }
88 |
89 | .menu-trigger {
90 | background-color: $background-color;
91 | color: $primary-color;
92 | transition: 0.5s;
93 | }
94 |
95 | .menu-trigger:hover {
96 | background-color: #272d47;
97 | }
98 |
99 | }
100 |
101 | // FOOTER STYLES
102 |
--------------------------------------------------------------------------------
/src/components/MiddleRow/Node.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from 'react';
2 | import { select, forceSimulation, forceLink, forceManyBody, forceCenter } from 'd3';
3 |
4 | function Node() {
5 |
6 | const svgRef = useRef();
7 |
8 | // const [newData, setNewData] = useState({
9 | // nodes: [
10 | // {"name": "Caql"},
11 | // {"name": "Aql"},
12 | // {"name": "Raql"},
13 | // {"name": "Jaql"},
14 | // {"name": "Maql"},
15 | // {"name": "Craql"}
16 | // ],
17 | // links: [
18 | // {"src": "Caql", "trg": "Jaql"},
19 | // {"src": "Craql", "trg": "Maql"},
20 | // {"src": "Maql", "trg": "Jaql"},
21 | // {"src": "Raql", "trg": "Caql"},
22 | // {"src": "Caql", "trg": "Aql"}
23 | // ]
24 | // });
25 |
26 | const [newNodes, setNewNodes] = useState([
27 | {"name": "Caql"},
28 | {"name": "Aql"},
29 | {"name": "Raql"},
30 | {"name": "Jaql"},
31 | {"name": "Maql"},
32 | {"name": "Craql"}
33 | ]);
34 |
35 | const [newLinks, setNewLinks] = useState([
36 | {"source": "Aql", "target": "Caql"},
37 | {"source": "Aql", "target": "Jaql"},
38 | {"source": "Aql", "target": "Maql"},
39 | {"source": "Aql", "target": "Raql"},
40 | {"source": "Aql", "target": "Craql"}
41 | ]);
42 |
43 | useEffect(() => {
44 |
45 | const svg = select(svgRef.current);
46 |
47 | let link = svg.selectAll("line")
48 | .data(newLinks)
49 | .enter()
50 | .append("line")
51 | .attr("stroke-width", 1)
52 | .style("stroke", "white");
53 |
54 | let node = svg.selectAll("circle")
55 | .data(newNodes)
56 | .enter()
57 | .append("circle")
58 | .attr("r", 5)
59 | .attr("fill", "teal");
60 |
61 | let simulation = forceSimulation()
62 | .force("link", forceLink().id((d) => d.name))
63 | .force("charge", forceManyBody())
64 | .force("center", forceCenter(100, 100));
65 |
66 | simulation
67 | .nodes(newNodes)
68 | .on("tick", ticked);
69 |
70 | simulation.force("link").links(newLinks);
71 |
72 | function ticked() {
73 | link
74 | .attr("x1", (d) => d.source.x)
75 | .attr("y1", (d) => d.source.y)
76 | .attr("x2", (d) => d.target.x)
77 | .attr("y2", (d) => d.target.y);
78 |
79 | node
80 | .attr("cx", (d) => d.x)
81 | .attr("cy", (d) => d.y);
82 | }
83 |
84 | }, [newNodes, newLinks]);
85 |
86 |
87 |
88 | return (
89 |
90 |
91 |
92 | );
93 | }
94 |
95 | export default Node;
96 |
--------------------------------------------------------------------------------
/server/controllers/traqlController.js:
--------------------------------------------------------------------------------
1 | const db = require('../model.js');
2 |
3 | const traqlController = {};
4 |
5 | traqlController.addAqlsToTraql = (req, res, next) => {
6 | let receivedTraqlEntries = req.body;
7 | let queryString = '';
8 | // create query string to send Aqls to the database
9 | if(receivedTraqlEntries.successAqls.length) {
10 | // create base of query string to send all values to db
11 | queryString += `insert into Aql (id, mutation_send_time, mutation_received_time, subscriber_received_time, latency, mutation_id, resolver, expected_subscribers, successful_subscribers, user_token) values `;
12 | for(let traql of receivedTraqlEntries.successAqls) {
13 | // loop through aqls in mutation Id
14 | for (let aql of traql.aqlsReceivedBack) {
15 | // add one aql of data to query string
16 | queryString += `('${aql.id}', '${aql.mutationSendTime}', '${aql.mutationReceived}', '${aql.subscriberReceived}', '${aql.roundtripTime}', '${aql.mutationId}', '${aql.resolver}', '${traql.expectedNumberOfAqls}', '${traql.aqlsReceivedBack.length}', '${aql.userToken}'),`;
17 | }
18 | }
19 | // format the query to remove final comma and add semicolon at end
20 | queryString = queryString.slice(0, -1) + ';';
21 | }
22 | if(receivedTraqlEntries.errorAqls.length) {
23 | for(let traql of receivedTraqlEntries.errorAqls) {
24 | if(traql.aqlsReceivedBack.length) {
25 | queryString += `insert into Aql (id, mutation_send_time, mutation_received_time, subscriber_received_time, latency, mutation_id, resolver, expected_subscribers, successful_subscribers, error, user_token) values `;
26 | for (let aql of traql.aqlsReceivedBack) {
27 | // add each aql to the query string
28 | queryString += `('${aql.id}', '${aql.mutationSendTime}', '${aql.mutationReceived}', '${aql.subscriberReceived}', '${aql.roundtripTime}', '${aql.mutationId}', '${aql.resolver}', ${traql.expectedNumberOfAqls}, ${traql.aqlsReceivedBack.length}, 'true', '${aql.userToken}'),`;
29 | }
30 | // format the query to remove final comma and add semicolon at end
31 | queryString = queryString.slice(0, -1) + ';';
32 | }
33 | // create error row for db with mutationID and traql stats
34 | queryString += `insert into Aql (mutation_id, mutation_received_time, resolver, expected_subscribers, successful_subscribers, error, user_token) values ('${traql.mutationId}', '${traql.openedTime}', '${traql.resolver}', '${traql.expectedNumberOfAqls}', '${traql.aqlsReceivedBack.length}', 'true', '${traql.userToken}');`;
35 | }
36 | }
37 | console.log('query string');
38 | console.log(queryString);
39 | db.query(queryString, (err, response) => {
40 | if (err) {
41 | return next(err);
42 | }
43 | console.log('Data successfully added to db');
44 | return next();
45 | });
46 | return next();
47 | };
48 |
49 | module.exports = traqlController;
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cors = require('cors');
3 | const GitHubStrategy = require('passport-github').Strategy;
4 | const passport = require('passport');
5 | const app = express();
6 | const PORT = 3000;
7 | const db = require('./model.js');
8 | const { v4: uuidv4 } = require('uuid');
9 | const authToken = require('./controllers/authTokenController.js');
10 | const Cookies = require ('js-cookie');
11 |
12 | const router = require('./router');
13 | const traqlRouter = require('./traqlRouter');
14 |
15 | app.use(express.json());
16 | app.use(express.urlencoded({ extended: true }));
17 | app.use(cors({ origin: ['http://localhost:8080', 'http://localhost:3000'] }));
18 | app.use(express.static('public'));
19 |
20 | app.use('/aqls', traqlRouter);
21 | app.use('/api', router);
22 |
23 | // ---------------------- GitHub OAuth Section ----------------------- //
24 |
25 | // Initialize Passport
26 | app.use(passport.initialize());
27 |
28 | // Incorporating GitHub strategy with environment variables
29 | passport.use(new GitHubStrategy({
30 | clientID: process.env.GITHUB_CLIENT_ID, // process.env.GITHUB_CLIENT_ID
31 | clientSecret: process.env.GITHUB_CLIENT_SECRET, // process.env.GITHUB_CLIENT_SECRET
32 | callbackURL: 'http://localhost:8080/auth/github/callback', // callback URL from GitHub
33 | },
34 | // Async callback function: params- tokens, GitHub profile, callback
35 | async (accessToken, refreshToken, profile, cb) => {
36 | // find profile in users table based on githubId
37 | const verifyUser = `SELECT * FROM users WHERE github_id = $1;`
38 | const githubId = [profile.id];
39 | const aqlsUser = await db.query(verifyUser, githubId);
40 |
41 | //insert this data to user table
42 | const signupQuery = `
43 | INSERT into users (
44 | username,
45 | display_name,
46 | github_id,
47 | avatar_url,
48 | user_token
49 | )
50 | VALUES ($1, $2, $3, $4, $5);
51 | `
52 | const singupArray = [
53 | profile.username,
54 | profile.displayName,
55 | profile.id,
56 | profile._json.avatar_url,
57 | uuidv4()
58 | ]
59 |
60 | //if aqlsUser does not exist in the database insert user data into user table
61 | if (!aqlsUser.rows.length) {
62 | db.query(signupQuery, singupArray)
63 | }
64 | cb(null, profile);
65 | }
66 | ));
67 |
68 | // Setting up Express routing for POST request to login via OAuth
69 | app.get('/githublogin', passport.authenticate('github', { session: false }));
70 |
71 | // Callback with GitHub OAuth to eventually redirect users to dashboard
72 | app.get(
73 | '/auth/github/callback',
74 | passport.authenticate('github', { session: false }),
75 | authToken.getToken,
76 | (req, res) => {
77 | res.locals.username = req.user.username;
78 | res.locals.id = req.user.id;
79 | res.locals.avatar = req.user._json.avatar_url;
80 | res.cookie('userToken', res.locals.token);
81 | res.redirect('/');
82 | }
83 | );
84 |
85 | module.exports = app.listen(PORT, () => {
86 | console.log('Aql hears you loud and clear on port 3000');
87 | });
88 |
89 |
--------------------------------------------------------------------------------
/server/helperFuncs.js:
--------------------------------------------------------------------------------
1 | //return obj with resolver names as properties and count as value
2 | function resolverStats(data) {
3 | // create returnObj
4 | const returnObj = {};
5 | // for element of data.rows
6 | for (let el of data.rows) {
7 | // if returnObj has key of element.resolver
8 | if (returnObj[el.resolver]) {
9 | // increment value
10 | returnObj[el.resolver] += 1;
11 | } else {
12 | // else make new key of value 1
13 | returnObj[el.resolver] = 1;
14 | }
15 | }
16 | const returnArr = [];
17 | for (let key in returnObj) {
18 | const resolverObj = {};
19 | resolverObj.name = key;
20 | resolverObj.value = returnObj[key];
21 | returnArr.push(resolverObj);
22 | }
23 | return returnArr;
24 | }
25 |
26 | // return object that represent the num of subs at every point in time where there was a mutation
27 | function subscriptionHistory(data) {
28 | // create returnObject
29 | const returnObj = {};
30 | // iterate through data
31 | for (let el of data.rows) {
32 | // if returnObj doesnt have key of mutationReceivedTime
33 | if (!returnObj[el.mutation_received_time]) {
34 | //create that key and set it to expected_subscribers
35 | returnObj[el.mutation_received_time] = el.expected_subscribers;
36 | }
37 | }
38 | // return returnObj
39 | return returnObj;
40 | }
41 |
42 | function mutations(data) {
43 | //create hashes to store aqls and errorAqls
44 | const hashql = {};
45 | let returnArr = [];
46 | const errorql = {};
47 | let errorArr = [];
48 |
49 | // getAvg function returns average latency of array of objects with latency property
50 | function getAvg(array) {
51 | let total = 0;
52 | if (array.length) {
53 | for (let el of array) {
54 | total += parseInt(el.latency);
55 | }
56 | total = total / array.length;
57 | }
58 | return Math.round(total);
59 | }
60 |
61 | for (let el of data.rows) {
62 | if (!el.error) {
63 | if (!hashql[el.mutation_id]) {
64 | hashql[el.mutation_id] = [el];
65 | } else {
66 | hashql[el.mutation_id].push(el);
67 | }
68 | } else {
69 | // return array of mutations that were errors, including any aqls that came back
70 | if (!errorql[el.mutation_id]) {
71 | // build error Mutation obj from shared data
72 | const errorMutObj = {};
73 | errorMutObj.mutationId = el.mutation_id;
74 | errorMutObj.resolver = el.resolver;
75 | errorMutObj.dateTime = el.mutation_received_time;
76 | errorMutObj.expected = el.expected_subscribers;
77 | errorMutObj.received = el.successful_subscribers;
78 | errorMutObj.aqls = el.id ? [el] : [];
79 | errorMutObj.error = true;
80 | errorql[el.mutation_id] = errorMutObj;
81 | } else {
82 | if (el.id) {
83 | errorql[el.mutation_id].aqls.push(el);
84 | }
85 | }
86 | }
87 | }
88 | // loop through hash and create properties on mutation object
89 | for (let key in hashql) {
90 | const mutObj = {};
91 | mutObj.mutationId = key;
92 | mutObj.resolver = hashql[key][0].resolver;
93 | mutObj.expectedAqls = hashql[key][0].expected_subscribers;
94 | mutObj.dateTime = hashql[key][0].mutation_received_time;
95 | mutObj.aqls = hashql[key];
96 | mutObj.avgLatency = getAvg(mutObj.aqls);
97 | returnArr.push(mutObj);
98 | }
99 | //calculate average latency of items in errorql
100 | for (let key in errorql) {
101 | errorql[key].avgLatency = getAvg(errorql[key].aqls);
102 | errorArr.push(errorql[key]);
103 | }
104 |
105 | //sort return arrays
106 | returnArr = returnArr.sort((a, b) => (a.dateTime > b.dateTime ? 1 : -1));
107 | errorArr = errorArr.sort((a, b) => (a.dateTime > b.dateTime ? 1 : -1));
108 | return [returnArr, errorArr];
109 | }
110 |
111 | module.exports = {
112 | resolverStats,
113 | subscriptionHistory,
114 | mutations,
115 | };
116 |
--------------------------------------------------------------------------------
/src/components/MiddleRow/PieChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | // import * as d3 from 'd3';
3 | import { Pie } from 'react-chartjs-2';
4 |
5 | const PieChart = (props) => {
6 | const state = {
7 | labels: props.resolverStats.map((elt) => elt.name),
8 | datasets: [
9 | {
10 | label: 'Resolvers',
11 | backgroundColor: ['#1167b1', '#40e0d0', '#187bcd', '#2a9df4'],
12 | hoverBackgroundColor: ['#187bcd', '#2a9df4', '#1167b1', '#d0efff'],
13 | data: props.resolverStats.map((elt) => elt.value),
14 | borderWidth: 0,
15 | },
16 | ],
17 | };
18 |
19 | const options = {
20 | legend: {
21 | display: true,
22 | position: 'bottom',
23 | padding: 0,
24 | align: 'center',
25 | labels: {
26 | boxWidth: 10,
27 | fontColor: 'lightgrey',
28 | },
29 | },
30 | };
31 | return (
32 |
37 | );
38 | };
39 |
40 | export default PieChart;
41 |
42 | // const Slice = (props) => {
43 | // let { pie } = props;
44 | // let arc = d3.arc().innerRadius(0).outerRadius(100);
45 |
46 | // let interpolate = d3.interpolateRgb('#eaaf79', '#bc3358');
47 |
48 | // return pie.map((slice, index) => {
49 | // let sliceColor = interpolate(index / (pie.length - 1));
50 |
51 | // return ;
52 | // });
53 | // };
54 |
55 | // export const PieChart = (props) => {
56 | // console.log('props.resolverStats:', props.resolverStats);
57 | // let data = props.resolverStats.map((elt) => elt.value);
58 | // let pie = d3.pie()(data);
59 |
60 | // return (
61 | //
66 | // );
67 | // };
68 |
69 | //-----------------FIRST-----------------//
70 |
71 | // const PieChart = (props) => {
72 |
73 | // let uselessData = [{name:'fds', value: 100}, {name:'fds', value: 150}];
74 | // const moreuseless = [1,2,3,4]
75 | // let dumData = props.dummyData.resolverStats.map(elt => elt);
76 | // console.log(dumData);
77 |
78 | // const ref = useRef();
79 |
80 | // const createPie = d3
81 | // .pie()
82 | // .value((d) => d.value)
83 | // .sort(null);
84 | // const createArc = d3
85 | // .arc()
86 | // .innerRadius(props.innerRadius)
87 | // .outerRadius(props.outerRadius);
88 | // const colors = d3.interpolateBlues;
89 |
90 | // useEffect(() => {
91 |
92 | // const data = createPie(dumData);
93 |
94 | // const group = d3.select(ref.current);
95 | // const groupWithData = group.selectAll('g.arc').data(data);
96 |
97 | // groupWithData
98 | // .enter()
99 | // .append('g')
100 | // .attr('class', 'arc');
101 |
102 | // const path = groupWithData
103 | // .append('path')
104 | // .merge(groupWithData.select('path.arc'));
105 |
106 | // path
107 | // .attr('class', 'arc')
108 | // .attr('d', createArc)
109 | // .attr('fill', (d, i) => colors(i/(props.resolverStats.length)));
110 |
111 | // const text = groupWithData
112 | // .append('text')
113 | // .merge(groupWithData.select('text'));
114 |
115 | // text
116 | // .attr('text-anchor', 'middle')
117 | // .attr('alignment-baseline', 'middle')
118 | // .attr('transform', (d) => `translate(${createArc.centroid(d)})`)
119 | // .style('fill', 'black')
120 | // .style('font-size', '10')
121 | // .style('font-family', 'Arial')
122 | // .style('text-align', 'middle')
123 | // //.text((d) => d.data.name);
124 | // }, []);
125 |
126 | // return (
127 | //
133 | // );
134 | // };
135 |
--------------------------------------------------------------------------------
/src/components/TopRow/LineChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Line } from 'react-chartjs-2';
3 | import moment from 'moment';
4 |
5 | function LineChart(props) {
6 | let [avgLatency, setAvgLatency] = useState(
7 | props.mutationData.map((elt) => parseInt(elt.avgLatency))
8 | );
9 | let [avgSubscribers, setAvgSubscribers] = useState(
10 | props.mutationData.map((elt) => parseInt(elt.expectedAqls))
11 | );
12 | let times = props.mutationData.map((el) => moment(parseInt(el.dateTime)));
13 | console.log(props);
14 | const data = {
15 | labels: times,
16 | datasets: [
17 | {
18 | label: 'Subscribers',
19 | fill: false,
20 | lineTension: 0.1,
21 | backgroundColor: 'rgba(75,192,192,0.4)',
22 | borderColor: 'dodgerblue',
23 | borderCapStyle: 'butt',
24 | borderDash: [],
25 | borderDashOffset: 0.0,
26 | borderJoinStyle: 'miter',
27 | pointBorderColor: 'dodgerblue',
28 | pointBackgroundColor: '#fff',
29 | pointBorderWidth: 1,
30 | pointHoverRadius: 5,
31 | pointHoverBackgroundColor: 'dodgerblue',
32 | pointHoverBorderColor: 'rgba(220,220,220,1)',
33 | pointHoverBorderWidth: 2,
34 | pointRadius: 1,
35 | pointHitRadius: 10,
36 | data: avgSubscribers,
37 | yAxisID: 'A',
38 | },
39 | {
40 | label: 'Average Latency',
41 | fill: false,
42 | lineTension: 0.1,
43 | backgroundColor: 'rgba(75,192,192,0.4)',
44 | borderColor: '#40E0D0',
45 | borderCapStyle: 'butt',
46 | borderDash: [],
47 | borderDashOffset: 0.0,
48 | borderJoinStyle: 'miter',
49 | pointBorderColor: 'dodgerblue',
50 | pointBackgroundColor: '#fff',
51 | pointBorderWidth: 1,
52 | pointHoverRadius: 5,
53 | pointHoverBackgroundColor: 'dodgerblue',
54 | pointHoverBorderColor: 'rgba(220,220,220,1)',
55 | pointHoverBorderWidth: 2,
56 | pointRadius: 1,
57 | pointHitRadius: 10,
58 | data: avgLatency,
59 | yAxisID: 'B',
60 | },
61 | ],
62 | };
63 | const options = {
64 | legend: {
65 | display: false,
66 | padding: 0,
67 | align: 'center',
68 | position: 'left',
69 | labels: {
70 | boxWidth: 10,
71 | fontColor: 'lightgrey',
72 | },
73 | },
74 | scales: {
75 | xAxes: [
76 | {
77 | type: 'time',
78 | position: 'bottom',
79 | time: {
80 | unit: 'day',
81 | displayFormats: {
82 | day: 'MMM D',
83 | },
84 | tooltipFormat: 'MMM D, h:mm:ss.SSS a',
85 | },
86 | distribution: 'series',
87 | ticks: {
88 | maxTicksLimit: 5,
89 | fontColor: 'lightgrey',
90 | fontSize: 9,
91 | },
92 | },
93 | ],
94 | yAxes: [
95 | {
96 | id: 'A',
97 | type: 'linear',
98 | position: 'left',
99 | scaleLabel: {
100 | display: true,
101 | labelString: 'Subscribers',
102 | fontColor: 'dodgerBlue',
103 | },
104 | ticks: {
105 | fontColor: 'lightgrey',
106 | fontSize: 9,
107 | },
108 | },
109 | {
110 | id: 'B',
111 | type: 'linear',
112 | position: 'right',
113 | scaleLabel: {
114 | display: true,
115 | labelString: 'Avg Latency',
116 | fontColor: 'turquoise',
117 | },
118 | ticks: {
119 | fontColor: 'lightgrey',
120 | fontSize: 9,
121 | },
122 | },
123 | ],
124 | },
125 | };
126 |
127 | const handleClick = () => {
128 | const newLatency = avgLatency;
129 | newLatency.pop();
130 | const newSubs = avgSubscribers;
131 | newSubs.pop();
132 | setAvgLatency(newLatency);
133 | setAvgSubscribers(newSubs);
134 | };
135 |
136 | return (
137 |
138 |
139 |
140 | );
141 | }
142 |
143 | export default LineChart;
144 |
--------------------------------------------------------------------------------
/dummydata.js:
--------------------------------------------------------------------------------
1 | const data = [
2 | {
3 | resolverStats: { newColor: 200, luckyNumber: 300 },
4 | subscriptionHistory: {
5 | 1601784033001: 1,
6 | 1601785976517: 2,
7 | 1601787564341: 3,
8 | },
9 | mutations: [
10 | {
11 | mutationId: '3fcc8d07-c4b5-477a-8dff-487f56dd3d88',
12 | resolver: 'newColor',
13 | avgLatency: 21,
14 | expectedAqls: 1,
15 | dateTime: 1601784033001,
16 | aqls: [
17 | {
18 | id: 'da7cb4a9-6387-4c0e-9d81-249c35755af1',
19 | mutation_send_time: '1601784033001',
20 | mutation_received_time: '1601784033008',
21 | subscriber_received_time: '1601784033022',
22 | latency: 21,
23 | mutation_id: '3fcc8d07-c4b5-477a-8dff-487f56dd3d88',
24 | resolver: 'newColor',
25 | expected_subscribers: '1',
26 | successful_subscribers: '1',
27 | error: null,
28 | user_token: null,
29 | },
30 | ],
31 | },
32 | {
33 | mutationId: '531fab47-e90d-4419-a886-ce311f633773',
34 | resolver: 'newColor',
35 | avgLatency: 89,
36 | expectedAqls: 2,
37 | dateTime: 1601785976517,
38 | aqls: [
39 | {
40 | id: 'e1ebe5dc-3bae-4d75-95ca-1244db6abe8f',
41 | mutation_send_time: '1601785976517',
42 | mutation_received_time: '1601785976545',
43 | subscriber_received_time: '1601785976553',
44 | latency: 36,
45 | mutation_id: '531fab47-e90d-4419-a886-ce311f633773',
46 | resolver: 'newColor',
47 | expected_subscribers: '2',
48 | successful_subscribers: '2',
49 | error: null,
50 | user_token: null,
51 | },
52 | {
53 | id: '9bd2720b-9a13-48e8-950e-d101b0758e1d',
54 | mutation_send_time: '1601785976517',
55 | mutation_received_time: '1601785976545',
56 | subscriber_received_time: '1601785976557',
57 | latency: 40,
58 | mutation_id: '531fab47-e90d-4419-a886-ce311f633773',
59 | resolver: 'newColor',
60 | expected_subscribers: '2',
61 | successful_subscribers: '2',
62 | error: null,
63 | user_token: null,
64 | },
65 | ],
66 | },
67 | {
68 | mutationId: '966604df-1b4a-48ae-9d54-a99f1c71a404',
69 | resolver: 'newColor',
70 | avgLatency: 72,
71 | expectedAqls: 3,
72 | dateTime: 1601787564341,
73 | aqls: [
74 | {
75 | id: '61e89e35-5a40-4c8c-9efe-49b91e662dba',
76 | mutation_send_time: '1601787564341',
77 | mutation_received_time: '1601787564346',
78 | subscriber_received_time: '1601787564348',
79 | latency: 7,
80 | mutation_id: '966604df-1b4a-48ae-9d54-a99f1c71a404',
81 | resolver: 'newColor',
82 | expected_subscribers: '3',
83 | successful_subscribers: '3',
84 | error: null,
85 | user_token: null,
86 | },
87 | {
88 | id: 'b241b477-1ece-43ad-a3a1-17fa40107fb1',
89 | mutation_send_time: '1601787564341',
90 | mutation_received_time: '1601787564346',
91 | subscriber_received_time: '1601787564348',
92 | latency: 7,
93 | mutation_id: '966604df-1b4a-48ae-9d54-a99f1c71a404',
94 | resolver: 'newColor',
95 | expected_subscribers: '3',
96 | successful_subscribers: '3',
97 | error: null,
98 | user_token: null,
99 | },
100 | {
101 | id: '51926f3c-3c2f-4d8c-bdfd-f9308ba45250',
102 | mutation_send_time: '1601787564341',
103 | mutation_received_time: '1601787564346',
104 | subscriber_received_time: '1601787564348',
105 | latency: 7,
106 | mutation_id: '966604df-1b4a-48ae-9d54-a99f1c71a404',
107 | resolver: 'newColor',
108 | expected_subscribers: '3',
109 | successful_subscribers: '3',
110 | error: null,
111 | user_token: null,
112 | },
113 | ],
114 | },
115 | ],
116 | errors: [
117 | {
118 | mutationId: 'adcebb0e-4e6c-473c-bada-76e8a7962c6a',
119 | resolver: 'newColor',
120 | avgTime: 'N/A',
121 | dateTime: 1601788872468,
122 | expected: 2,
123 | received: 0,
124 | aqls: [],
125 | },
126 | {
127 | mutationId: '424a35b6-d721-4f35-8acf-cae6e2584bbd',
128 | resolver: 'newColor',
129 | avgTime: '28',
130 | dateTime: 1601790770418,
131 | expected: 2,
132 | received: 1,
133 | aqls: [
134 | {
135 | id: 'c210ac95-774d-40cd-8aa8-7f93e280e95d',
136 | mutation_send_time: '1601790770418',
137 | mutation_received_time: '1601790770441',
138 | subscriber_received_time: '1601790770446',
139 | latency: 28,
140 | mutation_id: '424a35b6-d721-4f35-8acf-cae6e2584bbd',
141 | resolver: 'newColor',
142 | expected_subscribers: '2',
143 | successful_subscribers: '1',
144 | error: 'true',
145 | user_token: null,
146 | },
147 | ],
148 | },
149 | ],
150 | },
151 | ];
152 |
153 | export default data;
--------------------------------------------------------------------------------
/public/scss/dashboard.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 |
3 | // DASHBOARD STYLES
4 |
5 | .App {
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | align-items: center;
10 | font-family: Arial, Helvetica, sans-serif;
11 | }
12 |
13 | ::-webkit-scrollbar {
14 | display: none;
15 | }
16 |
17 | #dashboard-container {
18 | display: flex;
19 | flex-direction: column;
20 | justify-content: center;
21 | align-items: center;
22 | padding-top: 100px;
23 |
24 | width: 1024px;
25 |
26 | button {
27 | border-radius: 10%;
28 | background-color: $background-color;
29 | color: $primary-color;
30 | border: 1px solid $secondary-color;
31 | padding: 3, 5;
32 | }
33 |
34 | #top-row {
35 | display: flex;
36 | flex-wrap: wrap;
37 | justify-content: space-between;
38 | align-items: stretch;
39 | height: 35%;
40 | margin-bottom: 1rem;
41 | width: 100%;
42 | //padding: 0.5rem;
43 |
44 | #LineChartContainer {
45 | display: flex;
46 | flex-grow: 1;
47 | flex-direction: column;
48 | justify-content: space-between;
49 | align-items: center;
50 | height: 100%;
51 | border: 1px solid $primary-color;
52 | position: relative;
53 | }
54 |
55 | .LineChart {
56 | //justify-self: flex-end;
57 | // display: flex;
58 | // flex-direction: column;
59 | // justify-content: center;
60 | //width: 70%;
61 | }
62 | }
63 |
64 | #middle-row {
65 | display: flex;
66 | justify-content: space-between;
67 | align-items: stretch;
68 | width: 100%;
69 | height: 260px;
70 | padding-top: 20px;
71 |
72 | #PieChartContainer {
73 | display: flex;
74 | flex-direction: column;
75 | justify-content: center;
76 | align-items: center;
77 | width: 40%;
78 | height: 100%;
79 | border: 1px solid $primary-color;
80 | margin-right: 2rem;
81 | }
82 |
83 | // .PieCanvasDiv {
84 | // // height: 14rem;
85 | // //width: 28rem;
86 | // width: 100%;
87 | // height: auto;
88 | // position: relative;
89 | // }
90 |
91 | // .PieChart {
92 | // position: relative;
93 | // display: flex;
94 | // flex-direction: column;
95 | // align-items: center;
96 | // justify-content: center;
97 | // min-height: 90%;
98 | // min-width: 90%;
99 | // }
100 |
101 | #ErrorLogContainer {
102 | display: flex;
103 | flex-direction: column;
104 | flex-grow: 1;
105 | align-items: center;
106 | color: red;
107 | text-align: center;
108 | overflow: scroll;
109 | position: relative;
110 | border: 1px solid snow;
111 | }
112 |
113 | // #MapContainer {
114 | // display: flex;
115 | // flex-direction: column;
116 | // justify-content: center;
117 | // align-items: center;
118 | // width: 31.5%;
119 | // height: 100%;
120 | // border: 1px solid $primary-color;
121 | // }
122 |
123 | // #map {
124 | // }
125 |
126 | // #NodeContainer {
127 | // display: flex;
128 | // flex-direction: column;
129 | // justify-content: center;
130 | // align-items: center;
131 | // width: 31.5%;
132 | // height: 100%;
133 | // border: 1px solid $primary-color;
134 | // }
135 |
136 | // #node {
137 | // #node-label {
138 | // color: $primary-color;
139 | // }
140 | // }
141 |
142 | // #StatsContainer {
143 | // display: flex;
144 | // flex-direction: column;
145 | // justify-content: center;
146 | // align-items: left;
147 | // width: 31.5%;
148 | // height: 100%;
149 | // border: 1px solid $primary-color;
150 |
151 | // p {
152 | // font-family: Arial, Helvetica, sans-serif;
153 | // color: $primary-color;
154 | // font-size: 0.75rem;
155 | // margin: 10px;
156 | // }
157 | // }
158 |
159 | // #Stats {
160 | // }
161 | }
162 |
163 | #bottom-row {
164 | display: flex;
165 | flex-direction: column;
166 | justify-content: flex-start;
167 | align-items: center;
168 | margin-top: 20px;
169 | width: 100%;
170 | height: auto;
171 | border: 1px solid $primary-color;
172 | }
173 |
174 | #box-titles {
175 | display: flex;
176 | margin-bottom: 0.5rem;
177 | justify-content: center;
178 | align-items: center;
179 | text-align: center;
180 | width: 100%;
181 | color: snow;
182 | background-color: #2a2e42;
183 | height: 2.5rem;
184 | z-index: 3;
185 | }
186 |
187 | #LogContainer {
188 | display: flex;
189 | flex-direction: column;
190 | width: 100%;
191 | align-items: center;
192 | color: snow;
193 | text-align: center;
194 | height: 40vh;
195 | overflow: scroll;
196 | position: relative;
197 | }
198 |
199 | #log-header {
200 | display: flex;
201 | position: -webkit-sticky;
202 | position: sticky;
203 | top: 0;
204 | justify-content: space-between;
205 | align-items: center;
206 | background-color: #2a2e42;
207 | height: 2.5rem;
208 | z-index: 3;
209 | width: 1024px;
210 |
211 | div {
212 | flex: 1 1 0;
213 | }
214 |
215 | .wide-data {
216 | flex-grow: 2;
217 | }
218 |
219 | .spacer {
220 | max-width: 39px;
221 | }
222 | }
223 |
224 | .mutationRows {
225 | margin-top: 3rem;
226 | width: 100%;
227 | }
228 | .mutation-row {
229 | display: flex;
230 | flex-direction: column;
231 | justify-content: space-between;
232 | width: 100%;
233 | align-items: center;
234 | margin-bottom: 0rem;
235 | font-size: 0.8rem;
236 |
237 | div {
238 | flex: 1 1 0;
239 | }
240 |
241 | .wide-data {
242 | flex-grow: 2;
243 | }
244 |
245 | button {
246 | margin-left: 5px;
247 | height: auto;
248 | font-size: 1.5rem;
249 | border: none;
250 | outline: none !important;
251 | }
252 |
253 | .aql-container {
254 | background-color: #2a2e42;
255 | margin: 16px 10rem;
256 | height: inherit;
257 | width: 90%;
258 | div {
259 | flex: 1 1 0;
260 | }
261 |
262 | .wide-data {
263 | flex-grow: 2;
264 | }
265 |
266 | .aql-row-header {
267 | display: flex;
268 | justify-content: space-between;
269 | align-items: center;
270 | width: 100%;
271 | margin: 10px 0;
272 | }
273 |
274 | .aql-row {
275 | display: flex;
276 | justify-content: space-between;
277 | width: 100%;
278 | align-items: center;
279 | margin-bottom: 10px;
280 | background-color: #2a2e42;
281 | }
282 | }
283 | }
284 |
285 | .mutation-data {
286 | display: flex;
287 | justify-content: flex-start;
288 | width: 100%;
289 | align-items: center;
290 | }
291 | }
292 |
293 | .footer {
294 | display: flex;
295 | align-items: center;
296 | height: 3.5rem;
297 | color: snow;
298 | background-color: #2a2e42;
299 | position: fixed;
300 | bottom: 0;
301 | width: 100%;
302 |
303 | ul {
304 | display: flex;
305 | align-items: flex-start;
306 | list-style: none;
307 |
308 | li {
309 | margin-left: 2rem;
310 | }
311 | }
312 | }
313 |
--------------------------------------------------------------------------------
/src/components/TopRow/LineChart-old.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from 'react';
2 | import { Line } from 'react-chartjs-2'
3 | import {
4 | select,
5 | scaleLinear,
6 | scaleTime,
7 | line,
8 | min,
9 | max,
10 | curveCardinal,
11 | axisBottom,
12 | axisLeft,
13 | axisRight,
14 | timeFormat,
15 | zoom,
16 | zoomTransform,
17 | } from 'd3';
18 | import useResizeObserver from '../useResizeObserver';
19 |
20 |
21 |
22 | /**
23 | * Component that renders a LineChart
24 | */
25 |
26 | function LineChart(props) {
27 |
28 | let avgLatency = props.mutationData.map(elt => ({ avgL: elt.avgLatency, mutationDate: parseInt(elt.dateTime), subscribers: parseInt(elt.expectedAqls) }));
29 | let mutationLatencies = props.mutationData.map(elt => elt.avgLatency);
30 | let avgSubscribers = props.mutationData.map(elt => parseInt(elt.expectedAqls));
31 | let mutationDates = props.mutationData.map(elt => parseInt(elt.dateTime));
32 |
33 | mutationDates = mutationDates.slice(3);
34 | avgLatency = avgLatency.slice(3);
35 | avgSubscribers = avgSubscribers.slice(3);
36 | mutationLatencies = mutationLatencies.slice(3);
37 |
38 |
39 | // console.log(avgLatency.slice(0, 50));
40 |
41 | // console.log(mutationDates);
42 |
43 | const chartRef = useRef();
44 | // const wrapperRef = useRef();
45 | // const dimensions = useResizeObserver(wrapperRef);
46 | // const [currentZoomState, setCurrentZoomState] = useState();
47 |
48 | // // will be called initially and on every data change
49 | // useEffect(() => {
50 | // const svg = select(svgRef.current);
51 | // const svgContent = svg.select('.content');
52 | // console.log(svgContent)
53 | // const { width, height } = wrapperRef.current.getBoundingClientRect();
54 | // console.log(width, height)
55 | // // scales + line generator
56 | // const xScale = scaleTime()
57 | // .domain([min(mutationDates), max(mutationDates)])
58 | // .range([0, width]);
59 |
60 | // if (currentZoomState) {
61 | // const newXScale = currentZoomState.rescaleX(xScale);
62 | // xScale.domain(newXScale.domain());
63 | // }
64 |
65 | // const yScale = scaleLinear()
66 | // .domain([0, max(mutationLatencies) + 10])
67 | // .range([height - 10, 0]);
68 |
69 | // const ySubScale = scaleLinear()
70 | // .domain([0, max(avgSubscribers)])
71 | // .range([height - 10, 0]);
72 |
73 | // const latencyLine = line()
74 | // .x(d => xScale(d.mutationDate))
75 | // .y(d => yScale(d.avgL));
76 | // //.curve(curveCardinal);
77 |
78 | // const subscriberLine = line()
79 | // .x(d => xScale(d.mutationDate))
80 | // .y(d => ySubScale(d.subscribers));
81 | // //.curve(curveCardinal);
82 |
83 | // console.log(svgContent)
84 | // // render the line
85 | // // const children = svgContent.selectChildren()
86 |
87 | // // svgContent.remove(children)
88 |
89 | // svgContent
90 | // .append('path')
91 | // .data([avgLatency])
92 | // .attr('class', 'myLine')
93 | // .attr('stroke', 'lightblue')
94 | // .attr('fill', 'none')
95 | // .attr('d', latencyLine);
96 |
97 | // svgContent
98 | // .append('path')
99 | // .data([avgLatency])
100 | // .attr('class', 'myLine')
101 | // .style('stroke', 'dodgerblue')
102 | // .attr('fill', 'none')
103 | // .attr('d', subscriberLine);
104 |
105 | // // svgContent
106 | // // .selectAll('.myDot')
107 | // // .data(avgLatency)
108 | // // .join('circle')
109 | // // .attr('class', 'myDot')
110 | // // .attr('stroke', 'lightblue')
111 | // // .attr('r', 2)
112 | // // .attr('fill', 'lightblue')
113 | // // .attr('cx', (d) => xScale(d.mutationDate))
114 | // // .attr('cy', (d) => yScale(d.avgL));
115 |
116 | // // axes
117 | // const xAxis = axisBottom(xScale)
118 | // .tickFormat(timeFormat('%m-%dT%H:%M:%S'));
119 |
120 | // svg
121 | // .select('.x-axis')
122 | // .attr('transform', `translate(0, ${height})`)
123 | // .call(xAxis)
124 | // .style('color', 'white')
125 | // .selectAll('text')
126 | // .attr("transform", "translate(-10,10)rotate(-45)")
127 | // .style("text-anchor", "end")
128 | // // .style("font-size", 20)
129 |
130 |
131 | // const yAxis = axisLeft(yScale);
132 | // svg.select('.y-axis').style('color', 'white').call(yAxis);
133 |
134 | // const ySubAxis = axisRight(ySubScale);
135 | // svg.append('g')
136 | // .style('color', 'white')
137 | // .attr("transform", `translate( ${width},0)`)
138 | // .call(ySubAxis);
139 |
140 | // // zoom
141 | // const zoomBehavior = zoom()
142 | // .scaleExtent([0.5, 5])
143 | // .translateExtent([
144 | // [0, 0],
145 | // [width, height],
146 | // ])
147 | // .on('zoom', () => {
148 | // const zoomState = zoomTransform(svg.node());
149 | // setCurrentZoomState(zoomState);
150 | // });
151 |
152 | // // Add the labels to x and y-axes
153 | // svg.append("text")
154 | // .attr("text-anchor", "end")
155 | // .attr("transform", "rotate(-90)")
156 | // .attr("y", -50)
157 | // .attr("x", -30)
158 | // .text("Average Latency")
159 | // .style("font-family", "Arial")
160 | // .style("font-size", "12px")
161 | // .style("fill", "lightblue");
162 |
163 | // svg.append("text")
164 | // .attr("text-anchor", "end")
165 | // .attr("transform", "rotate(-90)")
166 | // .attr("y", `${width + 30}`)
167 | // .attr("x", -12)
168 | // .text("Number of Subscribers")
169 | // .style("font-family", "Arial")
170 | // .style("font-size", "12px")
171 | // .style("fill", "dodgerblue");
172 |
173 | // // svg.append("text")
174 | // // .attr("text-anchor", "end")
175 | // // .attr("y", 260)
176 | // // .attr("x", 210)
177 | // // .text("Time")
178 | // // .style("font-family", "Arial")
179 | // // .style("font-size", "12px")
180 | // // .style("fill", "white");
181 |
182 | // svg.call(zoomBehavior);
183 | // }, [currentZoomState, avgLatency, dimensions]);
184 | const data = {
185 | labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
186 | datasets: [
187 | {
188 | label: 'My First dataset',
189 | fill: false,
190 | lineTension: 0.1,
191 | backgroundColor: 'rgba(75,192,192,0.4)',
192 | borderColor: 'rgba(75,192,192,1)',
193 | borderCapStyle: 'butt',
194 | borderDash: [],
195 | borderDashOffset: 0.0,
196 | borderJoinStyle: 'miter',
197 | pointBorderColor: 'rgba(75,192,192,1)',
198 | pointBackgroundColor: '#fff',
199 | pointBorderWidth: 1,
200 | pointHoverRadius: 5,
201 | pointHoverBackgroundColor: 'rgba(75,192,192,1)',
202 | pointHoverBorderColor: 'rgba(220,220,220,1)',
203 | pointHoverBorderWidth: 2,
204 | pointRadius: 1,
205 | pointHitRadius: 10,
206 | data: [65, 59, 80, 81, 56, 55, 40]
207 | }
208 | ]
209 | };
210 |
211 | return (
212 | <>
213 |
214 |
215 | {/*
216 |
226 |
*/}
227 | >
228 | );
229 | }
230 |
231 | export default LineChart;
232 |
--------------------------------------------------------------------------------