18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | # About
26 |
27 | dashQL is a lightweight GraphQL caching tool utilizing Redis to cache queries at the field level to further improve reponse time and decrease the number of redundant requests required
28 |
29 | ## Table of Contents
30 | 1. [Features](https://github.com/oslabs-beta/dashQL/edit/main/README.md#features)
31 | 2. [Getting Started](https://github.com/oslabs-beta/dashQL/edit/main/README.md#getting-started)
32 | 3. [Demo](https://github.com/oslabs-beta/dashQL/edit/main/README.md#demo)
33 | 4. [Contributing](https://github.com/oslabs-beta/dashQL/edit/main/README.md#contributing-to-dashql)
34 | 5. [Authors](https://github.com/oslabs-beta/dashQL/edit/main/README.md#authors)
35 |
36 | # Features
37 | * Allows for caching at the field level from the GraphQL abstract syntax tree
38 | * Ability to handle query requests both with and without an argument
39 | * Ability to cache deeply nested queries
40 |
41 |
42 | # Getting Started
43 | 1. Install redis and define host and port in server file
44 | ```Javascript
45 | const redisClient = redis.createClient({
46 | host: "localhost",
47 | port: 6379,
48 | });
49 | ```
50 | 2. Download dashQL as an npm module and save it to your package.json as a dependency
51 | ```Javascript
52 | npm install dashql
53 | ```
54 | 3. Import dashQL into server file
55 | ```Javascript
56 | const dashQL = require('dashQL');
57 | ```
58 | 4. Create an instance of dashQL by passing in redisClient in server file
59 |
60 | ```Javascript
61 | const dashQL = new dashQL(redisClient);
62 | ```
63 |
64 | 5. On your server file which handles graphQL requests, name the endpoint '/dashQL' and put in dashQL as your middleware and return res.locals back to client
65 |
66 | ```Javascript
67 | app.use('/dashQL', dashQL, (req: Request, res: Response) => {
68 | return res.status(200).send(res.locals);
69 | });
70 | ```
71 |
72 | 6. Enjoy significantly faster response times specifically at the field level not just per query string!
73 |
74 |
75 |
76 | # Demo
77 | Feel free to visit our website to get an interactive demonstration of how our caching tool works and view the significantly improved response times
78 |
79 | After entering our site, you will be met with a demonstration with the ability to run GraphQL queries with an interactive selection tool utilizing the Star Wars API.
80 |
81 | 1. Select the fields you would like to query and a preview of the GraphQL query will be shown
82 |
83 | 
84 |
85 | 2. Click the 'Run Query' button to see the GraphQL query result. The metrics above will show the uncached response time populated on the graph and a cache hit/miss result will be logged to the bar charts. A cache miss will be logged the first time a unique query is run indicating that the query was not found in our cache and will be stored.
86 |
87 | 
88 |
89 |
90 | 3. This time, select an additional field to query. Then click the 'Run Query' button. The response time has drastically decreased as the previous field level queries were stored in the cache and the server only has to go to the database for one field.
91 |
92 | 
93 |
94 | 4. Feel free to play around with nested queries by selecting 'Species' or queries without an '_id' argument.
95 |
96 | 
97 | In this last example, you will notice the response time is decreased even further on the third query request as now all information is coming from the cache.
98 |
99 | 5. Lastly, click the 'Clear Cache' button to clear the cache and start over.
100 |
101 |
102 |
103 | # Contributing to dashQL
104 | The team at dashQL is excited you are interested in contributing to dashQL.
105 | 1. Fork repo from the `dev` branch
106 | 2. Create your feature branch (`git checkout -b feature/yourFeature`)
107 | 3. Stage and commit your changes (`git commit -m "added cool new feature"`)
108 | 4. Push to branch (`git push origin feature/yourFeature`)
109 | 5. Open a pull request to `dev` which will be reviewed by one of the dashQL authors prior to merge
110 |
111 | ## Roadmap for Future Development
112 | - [ ] Handle mutations
113 | - [ ] End-to-end testing
114 | - [ ] Additional eviction strategies
115 | - [ ] Security features
116 |
117 | # Authors
118 | Dan Bonney [LinkedIn](https://www.linkedin.com/in/dan-bonney/) | [Github](https://github.com/D-Bonney)
119 | Meredith Fronheiser [LinkedIn](https://www.linkedin.com/in/meredith-fronheiser/) | [Github](https://github.com/mfronheiser)
120 | Kevin Klochan [LinkedIn](https://www.linkedin.com/in/kevin-klochan-7a0ba7218/) | [Github](https://github.com/kevinklochan)
121 | Drew Williams [LinkedIn](https://www.linkedin.com/in/andrew-vaughan-williams/) | [Github](https://github.com/avwilliams1995)
122 |
123 |
124 |
125 | Please ⭐ our repo if you've found this useful, we want to be able to help as many of developers as we can!
126 |
--------------------------------------------------------------------------------
/__tests__/client/App.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "@testing-library/react";
2 | import { MemoryRouter } from "react-router-dom";
3 |
4 | import App from "../../client/App";
5 | import "react-router-dom";
6 |
7 | describe("App", () => {
8 |
9 | test("renders App", () => {
10 | render(
11 |
12 |
13 |
14 | );
15 |
16 | screen.debug();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/__tests__/client/api/apiFetch.test.tsx:
--------------------------------------------------------------------------------
1 | import getData from "../../../client/api/apiFetch";
2 |
3 | // mock for being able to render nav page
4 |
5 |
6 | describe("API Fetch Test", () => {
7 | const result: any = {
8 | peopleRes: null,
9 | planetsRes: null,
10 | nestedRes: null,
11 | peopleNoId: null,
12 | planetsNoId: null,
13 | };
14 | beforeEach(async () => {
15 | const pplStr = "query {people (_id:1){name, mass, , , }}";
16 | result.peopleRes = await getData({ query: pplStr });
17 | const planetStr = "query {planets (_id:1){name, population, , , }}";
18 | result.planetsRes = await getData({ query: planetStr });
19 | const nestedStr = "query {people (_id:1){name, , , species { name, } }}";
20 | result.nestedRes = await getData({ query: nestedStr });
21 | const peopleNoIdStr = "{peopleNoId {name, , , , }}";
22 | result.peopleNoId = await getData({ query: peopleNoIdStr });
23 | const planetsNoIdStr = "{planetsNoId {name, , , , }}";
24 | result.planetsNoId = await getData({ query: planetsNoIdStr });
25 | });
26 | test("Testing fetch to /dashQl endpoint for people schema with id", async () => {
27 | // tests
28 | expect(result.peopleRes.res).toStrictEqual(
29 | '{"data":{"people":{"name":"Luke Skywalker","mass":77}}}'
30 | );
31 | expect(result.peopleRes).toHaveProperty("res");
32 | expect(result.peopleRes).toHaveProperty("cacheHit");
33 | expect(result.peopleRes).toHaveProperty("hitPercentage");
34 | expect(result.peopleRes).toHaveProperty("missPercentage");
35 | expect(result.peopleRes).toHaveProperty("time");
36 | expect(result.peopleRes).toHaveProperty("totalHits");
37 | expect(result.peopleRes).toHaveProperty("totalQueries");
38 | });
39 |
40 | test("Testing fetch to /dashQl endpoint for planets schema with id", async () => {
41 | // tests
42 | expect(result.planetsRes.res).toStrictEqual(
43 | '{"data":{"planets":{"name":"Tatooine","population":200000}}}'
44 | );
45 | expect(result.planetsRes).toHaveProperty("res");
46 | expect(result.planetsRes).toHaveProperty("cacheHit");
47 | expect(result.planetsRes).toHaveProperty("hitPercentage");
48 | expect(result.planetsRes).toHaveProperty("missPercentage");
49 | expect(result.planetsRes).toHaveProperty("time");
50 | expect(result.planetsRes).toHaveProperty("totalHits");
51 | expect(result.planetsRes).toHaveProperty("totalQueries");
52 | });
53 |
54 | test("Testing fetch to /dashQl endpoint for nested people schema with id", async () => {
55 | // tests
56 | expect(result.nestedRes.res).toStrictEqual(
57 | '{"data":{"people":{"name":"Luke Skywalker","species":{"name":"Human"}}}}'
58 | );
59 | expect(result.nestedRes).toHaveProperty("res");
60 | expect(result.nestedRes).toHaveProperty("cacheHit");
61 | expect(result.nestedRes).toHaveProperty("hitPercentage");
62 | expect(result.nestedRes).toHaveProperty("missPercentage");
63 | expect(result.nestedRes).toHaveProperty("time");
64 | expect(result.nestedRes).toHaveProperty("totalHits");
65 | expect(result.nestedRes).toHaveProperty("totalQueries");
66 | });
67 |
68 | test("Testing fetch to /dashQl endpoint for people schema with NO id", async () => {
69 | // tests
70 | const parsedRes = await JSON.parse(result.peopleNoId.res);
71 | const expectedRes = [];
72 | for (let dataPoint of parsedRes.data.peopleNoId.name) {
73 | if (expectedRes.length >= 10) {
74 | break;
75 | }
76 | expectedRes.push(dataPoint);
77 | }
78 |
79 | expect(expectedRes).toStrictEqual([
80 | "Luke Skywalker",
81 | "C-3PO",
82 | "R2-D2",
83 | "Darth Vader",
84 | "Leia Organa",
85 | "Owen Lars",
86 | "Beru Whitesun lars",
87 | "R5-D4",
88 | "Biggs Darklighter",
89 | "Obi-Wan Kenobi",
90 | ]);
91 | expect(result.peopleNoId).toHaveProperty("res");
92 | expect(result.peopleNoId).toHaveProperty("cacheHit");
93 | expect(result.peopleNoId).toHaveProperty("hitPercentage");
94 | expect(result.peopleNoId).toHaveProperty("missPercentage");
95 | expect(result.peopleNoId).toHaveProperty("time");
96 | expect(result.peopleNoId).toHaveProperty("totalHits");
97 | expect(result.peopleNoId).toHaveProperty("totalQueries");
98 | });
99 |
100 | test("Testing fetch to /dashQl endpoint for planets schema with NO id", async () => {
101 | const parsedRes = await JSON.parse(result.planetsNoId.res);
102 | const expectedRes = [];
103 | for (let dataPoint of parsedRes.data.planetsNoId.name) {
104 | if (expectedRes.length >= 10) {
105 | break;
106 | }
107 | expectedRes.push(dataPoint);
108 | }
109 |
110 | expect(expectedRes).toStrictEqual([
111 | "Alderaan",
112 | "Yavin IV",
113 | "Hoth",
114 | "Dagobah",
115 | "Bespin",
116 | "Endor",
117 | "Naboo",
118 | "Coruscant",
119 | "Kamino",
120 | "Geonosis",
121 | ]);
122 | expect(result.planetsNoId).toHaveProperty("res");
123 | expect(result.planetsNoId).toHaveProperty("cacheHit");
124 | expect(result.planetsNoId).toHaveProperty("hitPercentage");
125 | expect(result.planetsNoId).toHaveProperty("missPercentage");
126 | expect(result.planetsNoId).toHaveProperty("time");
127 | expect(result.planetsNoId).toHaveProperty("totalHits");
128 | expect(result.planetsNoId).toHaveProperty("totalQueries");
129 | });
130 | });
131 |
--------------------------------------------------------------------------------
/__tests__/client/components/Demo.test.tsx:
--------------------------------------------------------------------------------
1 | // import { render, screen } from "@testing-library/react";
2 | // import Demo from "../../../client/components/Demo";
3 | // import { MemoryRouter } from "react-router-dom";
4 | // import { beforeEach } from "vitest";
5 | // import { useState } from "react";
6 |
7 | // let currentPage = "Demo";
8 | // function changePage(page: string): void {
9 | // currentPage = page;
10 | // }
11 |
12 | // describe("Demo Test", () => {
13 | // beforeEach(async () => {
14 | // render();
15 | // screen.debug()
16 | // });
17 |
18 | // test("renders Demo component", () => {
19 | // expect(screen.getByText("dashQL Cache Demo")).toBeInTheDocument();
20 | // });
21 | // });
22 |
--------------------------------------------------------------------------------
/__tests__/client/components/Nav.test.tsx:
--------------------------------------------------------------------------------
1 | // import { render, screen } from '@testing-library/react';
2 | // import Nav from '../../../client/components/Nav';
3 | // import { MemoryRouter } from 'react-router-dom';
4 | // import { beforeEach } from 'vitest';
5 |
6 | // // mock for being able to render nav page
7 | // const currentPage:string = "Home"
8 |
9 | // describe('Navbar Test', () => {
10 | // beforeEach(async () => {
11 | // render(
12 | //
13 | //
15 | // );
16 | // });
17 |
18 | // test('renders Navbar', () => {
19 | // // testing name elements showing with proper links
20 | // const navLinks = document.querySelector("#right-nav")?.children
21 | // const home = navLinks[0]
22 | // const demo = navLinks[1]
23 | // const docs = navLinks[2]
24 | // expect(screen.getByText('Home')).toBeInTheDocument();
25 | // expect(home.href).toBe("http://localhost:3000/");
26 |
27 | // expect(screen.getByText('Demo')).toBeInTheDocument();
28 | // expect(demo.href).toBe("http://localhost:3000/demo");
29 |
30 | // expect(screen.getByText('Docs')).toBeInTheDocument();
31 | // expect(docs.href).toBe("http://localhost:3000/docs");
32 |
33 | // // testing image elements showing (Hithub logo and dashql logo)
34 | // const testLogoImage = document.querySelector("img") as HTMLImageElement;
35 | // expect(testLogoImage.alt).toBe("dashQL logo");
36 | // expect(testLogoImage.src).toBe("http://localhost:3000/client/assets/dashQL_Logo.png");
37 |
38 | // const testGithubImage = document.querySelector("#githubLogo") as HTMLImageElement;
39 | // expect(testGithubImage.alt).toBe("github logo");
40 | // expect(testGithubImage.src).toBe("http://localhost:3000/client/assets/github-logo.png");
41 |
42 | // });
43 |
44 | // });
--------------------------------------------------------------------------------
/__tests__/client/components/QueryResult.test.tsx:
--------------------------------------------------------------------------------
1 | // import { expect, test } from 'vitest';
2 | // import QueryResult from '../../../client/components/Demo_Components/QueryResult';
3 | // import { render, screen } from '@testing-library/react';
4 |
5 | // test('QueryResult renders correctly with selected ID', async () => {
6 | // // Mock data and props
7 | // const data = {
8 | // res: '{"data":{"people":{"name":"Luke Skywalker","mass":"77","hair_color":"blond","eye_color":"blue"}}}',
9 | // };
10 | // const keys = ['name', 'mass', 'hair_color', 'eye_color'];
11 | // const currentDropdown = 'people';
12 | // const checkbox1 = true;
13 | // const checkbox2 = true;
14 | // const checkbox3 = true;
15 | // const checkbox4 = true;
16 | // const id = '1';
17 | // const dataField = 'people';
18 |
19 | // // Render the QueryResult component with props
20 | // const { getByText } = render(
21 | //
32 | // );
33 |
34 | // // expected output
35 | // expect(getByText('query {')).toBeInTheDocument();
36 | // expect(getByText('people (_id:1) {')).toBeInTheDocument();
37 | // expect(getByText('name: Luke Skywalker,')).toBeInTheDocument();
38 | // expect(getByText('mass: 77,')).toBeInTheDocument();
39 | // expect(getByText('hair_color: blond,')).toBeInTheDocument();
40 | // // expect(getByText('eye_color: blue,')).toBeInTheDocument();
41 | // // expect(getByText('}')).toBeInTheDocument();
42 | // });
43 |
44 | // test('QueryResult renders correctly without selected ID', async () => {
45 | // // Mock data and props
46 | // const data: any = {
47 | // res: {
48 | // data: {
49 | // peopleNoId: {
50 | // name: ["Luke Skywalker", "C-3PO", "R2-D2"]
51 | // }
52 | // }
53 | // }
54 | // }
55 | // const stringData = JSON.stringify(data.res)
56 | // data.res = stringData
57 |
58 | // const keys = ['name', 'mass', 'hair_color', 'eye_color'];
59 | // const currentDropdown = 'people';
60 | // const checkbox1 = true;
61 | // const checkbox2 = false;
62 | // const checkbox3 = false;
63 | // const checkbox4 = false;
64 | // const dataField = 'peopleNoId';
65 |
66 | // // Render the QueryResult component with props
67 | // const { getByText } = render(
68 | //
78 | // );
79 |
80 | // // Assert the expected output
81 | // expect(getByText('query {')).toBeInTheDocument();
82 | // expect(getByText('people {')).toBeInTheDocument();
83 | // expect(getByText('names:')).toBeInTheDocument();
84 | // expect(getByText('Luke Skywalker')).toBeInTheDocument();
85 | // expect(getByText('C-3PO')).toBeInTheDocument();
86 | // });
--------------------------------------------------------------------------------
/__tests__/setup.ts:
--------------------------------------------------------------------------------
1 | import { afterEach } from 'vitest';
2 | import { cleanup } from '@testing-library/react';
3 | import '@testing-library/jest-dom/vitest';
4 |
5 | // runs a cleanup after each test case (e.g. clearing jsdom)
6 | afterEach(() => {
7 | cleanup();
8 | });
--------------------------------------------------------------------------------
/client/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Khula;
3 | background: radial-gradient(
4 | circle,
5 | rgba(238, 174, 202, 0.1558998599439776) 0%,
6 | rgba(148, 187, 233, 0.07746848739495793) 100%
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/client/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import "./App.css";
3 | import { Routes, Route } from "react-router-dom";
4 | import Home from "./components/Home";
5 | import Nav from "./components/Nav";
6 | import Demo from "./components/Demo";
7 |
8 | function App() {
9 | const [currentPage, setPage] = useState("Home");
10 |
11 | function changePage(page: string): void {
12 | setPage(page);
13 | }
14 |
15 | return (
16 | <>
17 |
18 |
19 | } />
20 | } />
21 | {/* } /> */}
22 |
23 | >
24 | );
25 | }
26 |
27 | export default App;
28 |
--------------------------------------------------------------------------------
/client/api/apiFetch.tsx:
--------------------------------------------------------------------------------
1 | interface Querystr {
2 | query: string;
3 | }
4 |
5 | interface QueryResponse {
6 | res: any;
7 | time: number;
8 | cacheHit: boolean;
9 | }
10 |
11 | export default async function getData(
12 | finalQuery: Querystr
13 | ): Promise {
14 | const request: any = new Request(
15 | "https://dash-ql-backend.vercel.app/dashQL",
16 | {
17 | method: "POST",
18 | body: JSON.stringify(finalQuery),
19 | headers: { "Content-Type": "application/json" },
20 | }
21 | );
22 |
23 | const result: QueryResponse = await fetch(request)
24 | .then((res) => {
25 | return res.json();
26 | })
27 | .then((data) => {
28 | return data;
29 | })
30 | .catch((error) => {
31 | console.log(error, "error in fetching data");
32 | });
33 | return result;
34 | }
35 |
--------------------------------------------------------------------------------
/client/api/clearCache.tsx:
--------------------------------------------------------------------------------
1 | export default async function clearCache() {
2 | const request: RequestInfo = new Request(
3 | "https://dash-ql-backend.vercel.app/clearCache",
4 | {
5 | method: "GET",
6 | headers: { "Content-Type": "application/json" },
7 | }
8 | );
9 | await fetch(request)
10 | .then(() => {})
11 | .catch((error) => {
12 | console.log(error, "error in clearing cache");
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/client/assets/clock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/clock.png
--------------------------------------------------------------------------------
/client/assets/copyIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/copyIcon.png
--------------------------------------------------------------------------------
/client/assets/dan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/dan.png
--------------------------------------------------------------------------------
/client/assets/dashQL_Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/dashQL_Logo.png
--------------------------------------------------------------------------------
/client/assets/drew.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/drew.png
--------------------------------------------------------------------------------
/client/assets/fonts/Khula-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/fonts/Khula-Bold.ttf
--------------------------------------------------------------------------------
/client/assets/fonts/Khula-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/fonts/Khula-ExtraBold.ttf
--------------------------------------------------------------------------------
/client/assets/fonts/Khula-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/fonts/Khula-Light.ttf
--------------------------------------------------------------------------------
/client/assets/fonts/Khula-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/fonts/Khula-Regular.ttf
--------------------------------------------------------------------------------
/client/assets/fonts/Khula-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/fonts/Khula-SemiBold.ttf
--------------------------------------------------------------------------------
/client/assets/fonts/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, Erin McLaughlin (hello@erinmclaughlin.com). Digitized data copyright 2010, Google Corporation.
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | https://openfontlicense.org
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/client/assets/fronheiser.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/fronheiser.jpeg
--------------------------------------------------------------------------------
/client/assets/github-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/github-logo.png
--------------------------------------------------------------------------------
/client/assets/hands.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/hands.png
--------------------------------------------------------------------------------
/client/assets/kevin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/kevin.jpg
--------------------------------------------------------------------------------
/client/assets/linkedin-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/linkedin-logo.png
--------------------------------------------------------------------------------
/client/assets/save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dashQL/61efbc4935be41040bd06ae9344708d1f0cd97a1/client/assets/save.png
--------------------------------------------------------------------------------
/client/components/Contact.tsx:
--------------------------------------------------------------------------------
1 | import './styles/Contact.css';
2 | import ghLogo from '../assets/github-logo.png';
3 | import linkedinLogo from '../assets/linkedin-logo.png';
4 |
5 | export default function Contact(props: any) {
6 | return (
7 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/client/components/Demo.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import "./styles/Demo.css";
3 | import getData from "../api/apiFetch";
4 | import QueryResult from "./Demo_Components/QueryResult";
5 | import QueryCode from "./Demo_Components/queryCode";
6 | import "@fontsource-variable/source-code-pro";
7 | import BarChart from "./Demo_Components/BarChart";
8 | import PieChart from "./Demo_Components/PieChart";
9 | import LineChart from "./Demo_Components/LineChart";
10 | import ResultCard from "./Demo_Components/ResultCard";
11 | import clearCache from "../api/clearCache";
12 |
13 | // for which api part is selected by user (people, planets etc)
14 | type Fields = {
15 | name: string;
16 | mass?: string;
17 | species?: string;
18 | eye_color?: string;
19 | terrain?: string;
20 | climate?: string;
21 | species_name?: string;
22 | classification?: string;
23 | diameter?: number;
24 | };
25 |
26 | // people fields
27 | const defaultFields: Fields = {
28 | name: "",
29 | mass: "",
30 | eye_color: "",
31 | species: "",
32 | species_name: "",
33 | classification: "",
34 | };
35 |
36 | // planet fields
37 | const defaultPlanet: Fields = {
38 | name: "",
39 | diameter: 0,
40 | terrain: "",
41 | climate: "",
42 | };
43 |
44 | interface dataFormProps {
45 | changePage: (page: string) => void;
46 | }
47 |
48 | export default function Demo({ changePage }: dataFormProps) {
49 | // updates queryString and currentField (the default fields for what to display)
50 | const [queryString, setQueryString] = useState("");
51 | const [currentFields, setField] = useState(defaultFields);
52 | // have state for updating the dropdown. Depending on what dropwdown changes, will change the currentFields state
53 | const [currentDropdown, setDropdown] = useState("people");
54 | // states for each checkbox, first being the id box, then the 4 others, and nested checkboxes. This is needed in order to be able to update the displayed queries and query strings as the user is messing around with fields
55 | const [idBox, updateIdBox] = useState(true);
56 | const [checkboxes, updateCheckboxes] = useState([
57 | false,
58 | false,
59 | false,
60 | false,
61 | ]);
62 | const [nestedCheckboxes, updateNestedCheckboxes] = useState([
63 | false,
64 | false,
65 | ]);
66 | // if idBox is checked, this updates current id selected
67 | const [selectedId, setSelectedId] = useState("1");
68 | // data recieved from backend, queryData is data used when displaying results, and displayResults is a boolean to determine if they should be displayed or not based on if user is changing fields
69 | const [queryData, setQueryData] = useState({});
70 | const [displayResults, setDisplayResults] = useState(false);
71 | // chartData and cacheHits are the data stored from backend for the charts
72 | const [chartData, setChartData] = useState([]);
73 | const [hitsWithTotal, setHitsWithTotal] = useState([0, 0]);
74 | const [newPage, setNewPage] = useState(true);
75 | const [keys, setKeys] = useState([]);
76 |
77 | // set current page to Demo for Nav Bar visibility
78 |
79 | if (newPage) {
80 | setNewPage(false);
81 | clearCache();
82 | setTimeout(() => {
83 | changePage("Demo");
84 | }, 50);
85 | const fieldKeys: string[] = Object.keys(currentFields);
86 | setKeys(fieldKeys);
87 | }
88 |
89 | async function queryResult() {
90 | // function is called when "run query" button clicked. This will send of the query string, and alert the user (for now) if they haven't included the id and another checkbox
91 |
92 | if (!checkboxes[0] && !checkboxes[1] && !checkboxes[2] && !checkboxes[3]) {
93 | alert("Please select at least one attribute");
94 | return;
95 | }
96 | if (
97 | checkboxes[3] &&
98 | !nestedCheckboxes[0] &&
99 | !nestedCheckboxes[1] &&
100 | currentFields === defaultFields
101 | ) {
102 | alert("Please select at least one attribute from species");
103 | return;
104 | }
105 | // must send in object with query property due to how backend uses the request
106 | const result = await getData({ query: queryString });
107 | // get data from backend, update
108 | setQueryData(result);
109 | addData(result);
110 | setDisplayResults(true);
111 | }
112 |
113 | function addData(result: any) {
114 | // this function adds data to chartData after each query is ran
115 | const len: number = chartData.length + 1;
116 | type Data = {
117 | id: number;
118 | cacheHit: boolean;
119 | response_time: number;
120 | hitPercentage: number;
121 | missPercentage: number;
122 | };
123 |
124 | const newData: Data = {
125 | id: len,
126 | cacheHit: result.cacheHit,
127 | response_time: result.time,
128 | hitPercentage: result.hitPercentage * 100,
129 | missPercentage: result.missPercentage * 100,
130 | };
131 | setHitsWithTotal([
132 | hitsWithTotal[0] + result.totalHits,
133 | hitsWithTotal[1] + result.totalQueries,
134 | ]);
135 | setChartData([...chartData, newData]);
136 | }
137 |
138 | useEffect(() => {
139 | // when any checkbox is clicked or dropdown selection edits, will set fetch to false to empty "QUERY RESULTS" part of the dashboard, and will update the query string in order to update the "GraphQL Query" part of dashboard
140 | setDisplayResults(false);
141 | updateQueryString();
142 | }, [idBox, checkboxes, nestedCheckboxes, selectedId, currentDropdown]);
143 |
144 | // current checkboxes to be used for making the query string and query boxes
145 |
146 | async function updateQueryString() {
147 | // logic for creating the "GraphQL Query" display box of dashboard, as well as updating the string to be sent to backend, only invokes when useEffect triggered by change
148 | const firstBox: string = checkboxes[0] ? `${keys[0]}` : "";
149 | const secondBox: string = checkboxes[1] ? `${keys[1]}` : "";
150 | const thirdBox: string = checkboxes[2] ? `${keys[2]}` : "";
151 | let fourthBox: string;
152 | if (checkboxes[3] && currentFields === defaultFields) {
153 | fourthBox = `${keys[3]} {`;
154 | } else if (checkboxes[3] && currentFields !== defaultFields) {
155 | fourthBox = `${keys[3]}`;
156 | } else {
157 | fourthBox = "";
158 | }
159 | const firstNestedBox: string =
160 | nestedCheckboxes[0] && checkboxes[3] ? "name" : "";
161 | const secondNestedBox: string =
162 | nestedCheckboxes[1] && checkboxes[3] ? `${keys[5]}` : "";
163 | const idWanted: string = idBox ? `(_id:${selectedId})` : "";
164 | const end: string | null =
165 | !firstBox && !secondBox && !thirdBox && !fourthBox ? null : `}`;
166 | const nestedEnd: string = firstNestedBox || secondNestedBox ? "}" : "";
167 | const dropdown: string = idBox ? currentDropdown : `${currentDropdown}NoId`;
168 | const result: string = `query {${dropdown} ${idWanted}{${firstBox}, ${secondBox}, ${thirdBox}, ${fourthBox} ${firstNestedBox}, ${secondNestedBox} ${nestedEnd} ${end}}`;
169 | setQueryString(result);
170 | }
171 |
172 | function changeIdBox() {
173 | updateIdBox(!idBox);
174 | updateCheckboxes([false, false, false, false]);
175 | updateNestedCheckboxes([false, false]);
176 | setDisplayResults(false);
177 | }
178 |
179 | function changeDropdown(event: any) {
180 | // invokes when user changes dropdown value
181 | if (event.target.value === "people") {
182 | setField(defaultFields);
183 | setKeys(Object.keys(defaultFields));
184 | } else if (event.target.value === "planets") {
185 | setField(defaultPlanet);
186 | setKeys(Object.keys(defaultPlanet));
187 | }
188 | setDropdown(event.target.value);
189 | setSelectedId("1");
190 | setDisplayResults(false);
191 | updateCheckboxes([false, false, false, false]);
192 | updateNestedCheckboxes([false, false]);
193 | }
194 |
195 | function updateCheckboxesFunc(index: any) {
196 | const currCheckboxes: boolean[] = [...checkboxes];
197 | currCheckboxes[index] = !checkboxes[index];
198 | updateCheckboxes(currCheckboxes);
199 | }
200 |
201 | function updateNestedCheckboxesFunc(index: any) {
202 | const currNestedCheckboxes: boolean[] = [...nestedCheckboxes];
203 | currNestedCheckboxes[index] = !nestedCheckboxes[index];
204 | updateNestedCheckboxes(currNestedCheckboxes);
205 | }
206 |
207 | function updateBoxFour() {
208 | const currCheckboxes: boolean[] = [...checkboxes];
209 | currCheckboxes[3] = !checkboxes[3];
210 | updateCheckboxes(currCheckboxes);
211 | if (!checkboxes[3]) {
212 | updateNestedCheckboxes([false, false]);
213 | }
214 | setDisplayResults(false);
215 | }
216 |
217 | function changeId(event: any) {
218 | // invokes when user changes id value
219 | updateCheckboxes([false, false, false, false]);
220 | updateNestedCheckboxes([false, false]);
221 | setSelectedId(event.target.value);
222 | }
223 |
224 | // invoked when clear cache button is clicked
225 | function resetAll() {
226 | // clear backend cache
227 | clearCache();
228 | // resets all states
229 | setQueryString("");
230 | setField(defaultFields);
231 | setDropdown("people");
232 | updateCheckboxes([false, false, false, false]);
233 | updateNestedCheckboxes([false, false]);
234 | setQueryData("");
235 | setSelectedId("1");
236 | setDisplayResults(false);
237 | setChartData([]);
238 | setHitsWithTotal([0, 0]);
239 | }
240 |
241 | return (
242 |
243 |
dashQL Cache Demo
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
Star Wars API
265 |
Select the fields to query:
266 |
274 |
275 |
283 |
284 | {idBox ? (
285 | // only show id dropdown if id box is checked
286 | // can have a way to populate this field with more id's based on query selection
287 |
302 | ) : null}
303 |
12 | // }
--------------------------------------------------------------------------------
/client/components/Home.tsx:
--------------------------------------------------------------------------------
1 | import './styles/Home.css';
2 | import copyImage from '../assets/copyIcon.png';
3 | import saveImage from '../assets/save.png';
4 | import handsImage from '../assets/hands.png';
5 | import clockImage from '../assets/clock.png';
6 | import drewImg from '../assets/drew.png';
7 | import danImg from '../assets/dan.png';
8 | import meredithImg from '../assets/fronheiser.jpeg';
9 | import kevinImg from '../assets/kevin.jpg';
10 | import Contact from './Contact.tsx';
11 |
12 | export default function Home() {
13 | return (
14 |
15 |
16 |
Welcome to dashQL
17 |
18 |
19 | A dashing and dynamic caching tool for GraphQL.
20 |
21 |
22 |
23 |
24 |
25 | $ npm install dashQL
26 |
27 |
28 |
36 |
37 |
38 |
39 |
What is dashQL?
40 |
41 | DashQL is an open-source caching solution for GraphQL, which leverages
42 | an advanced algorithm that breaks down a GraphQL query into its
43 | smallest parts before performing caching logic on it. This results in
44 | unmatched performance benefits, translating into a more tactile and
45 | responsive experience for the users of your application.
46 |