From fda6a5c0313291e5f1eba3432e0c3785b0fcb828 Mon Sep 17 00:00:00 2001
From: Leonardo Christino <leomilho@gmail.com>
Date: Mon, 17 Jul 2023 09:50:55 +0200
Subject: [PATCH] feat(query): enable backend connection

---
 apps/web/.env                                 |  4 +-
 apps/web/.env.development                     |  7 ++
 apps/web/.env.production                      |  9 +-
 apps/web/node.d.ts                            |  1 +
 apps/web/package.json                         | 96 +++++++++----------
 apps/web/src/app/app.tsx                      | 14 +--
 apps/web/src/components/navbar/navbar.tsx     |  5 +-
 apps/web/src/environments/variables.ts        |  3 +-
 apps/web/vite.config.ts                       |  3 +-
 libs/shared/.env                              |  3 -
 libs/shared/lib/data-access/api/database.ts   | 10 +-
 libs/shared/lib/data-access/api/query.ts      |  7 +-
 libs/shared/lib/data-access/api/schema.ts     |  6 +-
 libs/shared/lib/data-access/api/user.ts       |  5 +-
 .../lib/data-access/authorization/useAuth.jsx | 27 ++++--
 .../WebSocketHandler.tsx                      |  2 +-
 .../shared/lib/data-access/store/authSlice.ts | 12 ++-
 .../panel/querybuilder.module.scss            | 75 ++++++++-------
 .../lib/querybuilder/panel/querybuilder.tsx   | 43 ++++++++-
 .../customFlowPills/entitypill/entitypill.tsx |  4 +-
 .../schema/pills/nodes/entity/entity-node.tsx | 16 ++--
 libs/shared/node.d.ts                         | 12 +++
 turbo.json                                    | 40 ++++----
 23 files changed, 246 insertions(+), 158 deletions(-)
 create mode 100644 apps/web/.env.development
 delete mode 100644 libs/shared/.env
 create mode 100644 libs/shared/node.d.ts

diff --git a/apps/web/.env b/apps/web/.env
index f651adbd0..31d4bea3b 100644
--- a/apps/web/.env
+++ b/apps/web/.env
@@ -1,2 +1,2 @@
-VITE_BACKEND_URL=api.graphpolaris.com
-VITE_STAGING=local
+VITE_BACKEND_URL=https://api.graphpolaris.com
+VITE_STAGING=local
diff --git a/apps/web/.env.development b/apps/web/.env.development
new file mode 100644
index 000000000..f6b68d9b0
--- /dev/null
+++ b/apps/web/.env.development
@@ -0,0 +1,7 @@
+VITE_BACKEND_URL=http://localhost
+VITE_BACKEND_WSS_URL=ws://localhost:3001/
+VITE_STAGING=dev
+VITE_SKIP_LOGIN=true
+VITE_BACKEND_USER=:3000
+VITE_BACKEND_QUERY=:8080
+VITE_BACKEND_SCHEMA=:3002
\ No newline at end of file
diff --git a/apps/web/.env.production b/apps/web/.env.production
index 895239e15..cf5c3e8e8 100644
--- a/apps/web/.env.production
+++ b/apps/web/.env.production
@@ -1,2 +1,7 @@
-VITE_BACKEND_URL=api.graphpolaris.com
-VITE_STAGING=prod
\ No newline at end of file
+VITE_BACKEND_URL=https://api.graphpolaris.com
+VITE_BACKEND_WSS_URL=ws://api.graphpolaris.com/socket/
+VITE_STAGING=prod
+VITE_SKIP_LOGIN=false
+VITE_BACKEND_USER=/user
+VITE_BACKEND_QUERY=:8080
+VITE_BACKEND_SCHEMA=/schema
\ No newline at end of file
diff --git a/apps/web/node.d.ts b/apps/web/node.d.ts
index 515e679e7..34f3d984d 100644
--- a/apps/web/node.d.ts
+++ b/apps/web/node.d.ts
@@ -1,6 +1,7 @@
 interface ImportMeta {
   env: {
     VITE_BACKEND_URL: string;
+    VITE_BACKEND_WSS_URL: string;
     VITE_STAGING: string;
     VITE_KEYCLOAK_URL: string;
     VITE_KEYCLOAK_REALM: string;
diff --git a/apps/web/package.json b/apps/web/package.json
index 9dc529d75..6de2cebe7 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -1,48 +1,48 @@
-{
-  "name": "web",
-  "private": true,
-  "version": "0.0.0",
-  "type": "module",
-  "scripts": {
-    "dev": "vite --host local.graphpolaris.com --port 4200",
-    "dev2": "vite --host local.graphpolaris.com --port 4200",
-    "build": "tsc && vite build",
-    "preview": "vite preview",
-    "lint": "eslint *.ts*",
-    "test": "vitest run"
-  },
-  "dependencies": {
-    "@graphpolaris/shared": "workspace:*",
-    "@mui/base": "5.0.0-alpha.118",
-    "@mui/icons-material": "^5.11.11",
-    "@mui/material": "^5.11.13",
-    "@reduxjs/toolkit": "^1.9.2",
-    "graphology": "^0.25.1",
-    "react": "^18.2.0",
-    "react-dom": "^18.2.0",
-    "react-grid-layout": "^1.3.4",
-    "react-redux": "^8.0.5",
-    "react-router-dom": "^6.8.1",
-    "reactflow": "11.4.0-next.1",
-    "styled-components": "^5.3.6"
-  },
-  "devDependencies": {
-    "@storybook/react": "7.0.0-rc.5",
-    "@testing-library/react": "14.0.0",
-    "@types/react": "^18.0.28",
-    "@types/react-dom": "^18.0.11",
-    "@types/react-grid-layout": "^1.3.2",
-    "@types/styled-components": "^5.1.26",
-    "@vitejs/plugin-basic-ssl": "^1.0.1",
-    "@vitejs/plugin-react-swc": "^3.0.0",
-    "autoprefixer": "^10.4.14",
-    "graphology-types": "^0.24.7",
-    "postcss": "^8.4.21",
-    "react-is": "^18.2.0",
-    "tailwindcss": "^3.3.1",
-    "typescript": "^4.9.3",
-    "vite": "^4.2.0",
-    "vite-plugin-dts": "^2.1.0",
-    "vitest": "^0.29.2"
-  }
-}
+{
+  "name": "web",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev2": "vite --host local.graphpolaris.com --port 4200",
+    "dev": "vite --port 4200",
+    "build": "tsc && vite build",
+    "preview": "vite preview",
+    "lint": "eslint *.ts*",
+    "test": "vitest run"
+  },
+  "dependencies": {
+    "@graphpolaris/shared": "workspace:*",
+    "@mui/base": "5.0.0-alpha.118",
+    "@mui/icons-material": "^5.11.11",
+    "@mui/material": "^5.11.13",
+    "@reduxjs/toolkit": "^1.9.2",
+    "graphology": "^0.25.1",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "react-grid-layout": "^1.3.4",
+    "react-redux": "^8.0.5",
+    "react-router-dom": "^6.8.1",
+    "reactflow": "11.4.0-next.1",
+    "styled-components": "^5.3.6"
+  },
+  "devDependencies": {
+    "@storybook/react": "7.0.0-rc.5",
+    "@testing-library/react": "14.0.0",
+    "@types/react": "^18.0.28",
+    "@types/react-dom": "^18.0.11",
+    "@types/react-grid-layout": "^1.3.2",
+    "@types/styled-components": "^5.1.26",
+    "@vitejs/plugin-basic-ssl": "^1.0.1",
+    "@vitejs/plugin-react-swc": "^3.0.0",
+    "autoprefixer": "^10.4.14",
+    "graphology-types": "^0.24.7",
+    "postcss": "^8.4.21",
+    "react-is": "^18.2.0",
+    "tailwindcss": "^3.3.1",
+    "typescript": "^4.9.3",
+    "vite": "^4.2.0",
+    "vite-plugin-dts": "^2.1.0",
+    "vitest": "^0.29.2"
+  }
+}
diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx
index 3e0c20dfa..97e18077a 100644
--- a/apps/web/src/app/app.tsx
+++ b/apps/web/src/app/app.tsx
@@ -18,7 +18,6 @@ import { Schema } from '@graphpolaris/shared/lib/schema/panel';
 import { useEffect, useRef, useState } from 'react';
 import { Navbar } from '../components/navbar/navbar';
 import Panel from '../components/panels/panel';
-import { domain } from '../environments/variables';
 import { VisualizationPanel } from './panels/Visualization';
 import styles from './app.module.scss';
 import { logout } from '@graphpolaris/shared/lib/data-access/store/authSlice';
@@ -31,14 +30,14 @@ export interface App {
 export function App(props: App) {
   const isLogin = useAuth();
   const auth = useAuthorizationCache();
-  const api = useDatabaseAPI(domain);
-  const api_schema = useSchemaAPI(domain);
-  const api_query = useQueryAPI(domain);
+  const api = useDatabaseAPI();
+  const api_schema = useSchemaAPI();
+  const api_query = useQueryAPI();
   const dispatch = useAppDispatch();
   const session = useSessionCache();
   const query = useQuerybuilderGraph();
   const queryHash = useQuerybuilderHash();
-  const ws = useRef(new WebSocketHandler(domain));
+  const ws = useRef(new WebSocketHandler(import.meta.env.VITE_BACKEND_WSS_URL));
   const [authCheck, setAuthCheck] = useState(false);
 
   // for testing purposes
@@ -69,14 +68,15 @@ export function App(props: App) {
 
   useEffect(() => {
     // Newly (un)authorized
+    console.log('Auth changed', auth.authorized, isLogin);
     if (auth.authorized) {
       console.info('App is authorized; Getting Databases', isLogin);
       api.GetAllDatabases({ updateSessionCache: true });
       setAuthCheck(true);
     } else {
-      dispatch(logout());
+      // dispatch(logout());
     }
-  }, [isLogin]);
+  }, [auth]);
 
   useEffect(() => {
     // New query
diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx
index ba1cae518..c5918591a 100644
--- a/apps/web/src/components/navbar/navbar.tsx
+++ b/apps/web/src/components/navbar/navbar.tsx
@@ -26,7 +26,6 @@ import {
   useSchemaAPI,
   useSessionCache,
 } from '@graphpolaris/shared/lib/data-access';
-import { domain } from '../../environments/variables';
 
 /** NavbarComponentProps is an interface containing the NavbarViewModel. */
 export interface NavbarComponentProps {
@@ -54,8 +53,8 @@ export const Navbar = (props: NavbarComponentProps) => {
   const theme = useTheme();
   const auth = useAuthorizationCache();
   const session = useSessionCache();
-  const api = useDatabaseAPI(domain);
-  const schemaApi = useSchemaAPI(domain);
+  const api = useDatabaseAPI();
+  const schemaApi = useSchemaAPI();
   const dispatch = useAppDispatch();
 
   useEffect(() => {
diff --git a/apps/web/src/environments/variables.ts b/apps/web/src/environments/variables.ts
index 51edd0dc1..b8c2d6b48 100644
--- a/apps/web/src/environments/variables.ts
+++ b/apps/web/src/environments/variables.ts
@@ -1 +1,2 @@
-export const domain = import.meta.env.VITE_BACKEND_URL;
+// export const domainPrefix = import.meta.env.VITE_STAGING === 'development' ? '' : 'https://';
+// export const domain = domainPrefix + import.meta.env.VITE_BACKEND_URL;
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts
index f4e7dada1..44c0c7d60 100644
--- a/apps/web/vite.config.ts
+++ b/apps/web/vite.config.ts
@@ -2,14 +2,13 @@
 import { defineConfig } from 'vite';
 import react from '@vitejs/plugin-react-swc';
 import path from 'path';
-import basicSsl from '@vitejs/plugin-basic-ssl';
 import dts from 'vite-plugin-dts';
 
 // https://vitejs.dev/config/
 export default defineConfig({
   plugins: [
     react(),
-    basicSsl(),
+    // basicSsl(),
     dts({
       insertTypesEntry: true,
     }),
diff --git a/libs/shared/.env b/libs/shared/.env
deleted file mode 100644
index 25874d424..000000000
--- a/libs/shared/.env
+++ /dev/null
@@ -1,3 +0,0 @@
-VITE_KEYCLOAK_URL=https://login.graphpolaris.com/  
-VITE_KEYCLOAK_REALM=graphpolaris
-VITE_KEYCLOAK_CLIENT=web
\ No newline at end of file
diff --git a/libs/shared/lib/data-access/api/database.ts b/libs/shared/lib/data-access/api/database.ts
index cfc2620ab..bb7390c02 100644
--- a/libs/shared/lib/data-access/api/database.ts
+++ b/libs/shared/lib/data-access/api/database.ts
@@ -33,15 +33,17 @@ export type DeleteDatabasesOptions = {
   updateSessionCache?: boolean;
 };
 
-export const useDatabaseAPI = (domain: string) => {
+export const useDatabaseAPI = () => {
   const { accessToken } = useAuthorizationCache();
   const cache = useSessionCache();
   const dispatch = useAppDispatch();
+  const domain = import.meta.env.VITE_BACKEND_URL;
+  const useruri = import.meta.env.VITE_BACKEND_USER;
 
   function AddDatabase(request: AddDatabaseRequest, options: AddDatabaseOptions = {}): Promise<void> {
     const { setAsCurrent = true, updateDatabaseCache = false } = options;
     return new Promise((resolve, reject) => {
-      fetch(`https://${domain}/user/database`, {
+      fetch(`${domain}${useruri}/database`, {
         method: 'POST',
         credentials: 'same-origin',
         headers: new Headers({
@@ -65,7 +67,7 @@ export const useDatabaseAPI = (domain: string) => {
   async function GetAllDatabases(options: GetDatabasesOptions = {}): Promise<Array<string>> {
     const { updateSessionCache: updateDatabaseCache = true } = options;
     // console.log(accessToken);
-    const response = await fetch(`https://${domain}/user/database`, {
+    const response = await fetch(`${domain}${useruri}/database`, {
       method: 'GET',
       credentials: 'same-origin',
       headers: new Headers({
@@ -88,7 +90,7 @@ export const useDatabaseAPI = (domain: string) => {
   function DeleteDatabase(name: string, options: DeleteDatabasesOptions = {}): Promise<void> {
     const { updateSessionCache: updateDatabaseCache = true } = options;
     return new Promise((resolve, reject) => {
-      fetch(`https://${domain}/user/database/` + name, {
+      fetch(`${domain}${useruri}/database/` + name, {
         method: 'DELETE',
         credentials: 'same-origin',
         headers: new Headers({
diff --git a/libs/shared/lib/data-access/api/query.ts b/libs/shared/lib/data-access/api/query.ts
index ad8d5e0f7..8f5c9da66 100644
--- a/libs/shared/lib/data-access/api/query.ts
+++ b/libs/shared/lib/data-access/api/query.ts
@@ -3,12 +3,13 @@
 import { BackendQueryFormat } from '../../querybuilder/model/BackendQueryFormat';
 import { useAuthorizationCache, useSessionCache } from '../store';
 
-export const useQueryAPI = (domain: string) => {
+export const useQueryAPI = () => {
   const cache = useSessionCache();
   const { accessToken } = useAuthorizationCache();
+  const domain = import.meta.env.VITE_BACKEND_URL;
 
   async function execute(query: BackendQueryFormat) {
-    const response = await fetch(`https://${domain}/query/execute/`, {
+    const response = await fetch(`${domain}/query/execute/`, {
       method: 'POST',
       credentials: 'same-origin',
       headers: new Headers({
@@ -28,7 +29,7 @@ export const useQueryAPI = (domain: string) => {
 
   async function retrieveCachedQuery(queryID: string) {
     // TODO: check if this method is needed!
-    // const response = await fetch(`https://${domain}/query/retrieve-cached/`, {
+    // const response = await fetch(`${domain}/query/retrieve-cached/`, {
     //     method: 'POST',
     //     credentials: 'same-origin',
     //     headers: new Headers({
diff --git a/libs/shared/lib/data-access/api/schema.ts b/libs/shared/lib/data-access/api/schema.ts
index 67d2be290..59a7b2ecf 100644
--- a/libs/shared/lib/data-access/api/schema.ts
+++ b/libs/shared/lib/data-access/api/schema.ts
@@ -2,9 +2,11 @@
 
 import { useAuthorizationCache, useSessionCache } from '../store';
 
-export const useSchemaAPI = (domain: string) => {
+export const useSchemaAPI = () => {
   const cache = useSessionCache();
   const { accessToken } = useAuthorizationCache();
+  const domain = import.meta.env.VITE_BACKEND_URL;
+  const schema = import.meta.env.VITE_BACKEND_SCHEMA;
 
   async function RequestSchema(databaseName?: string) {
     if (!databaseName) databaseName = cache.currentDatabase;
@@ -17,7 +19,7 @@ export const useSchemaAPI = (domain: string) => {
       cached: true,
     };
 
-    const response = await fetch(`https://${domain}/schema/`, {
+    const response = await fetch(`${domain}${schema}/`, {
       method: 'POST',
       credentials: 'same-origin',
       headers: new Headers({
diff --git a/libs/shared/lib/data-access/api/user.ts b/libs/shared/lib/data-access/api/user.ts
index a4fb38603..5e4cd7e3d 100644
--- a/libs/shared/lib/data-access/api/user.ts
+++ b/libs/shared/lib/data-access/api/user.ts
@@ -8,12 +8,13 @@ export type User = {
   SignInProvider: number;
 };
 
-export const useUserAPI = (domain: string) => {
+export const useUserAPI = () => {
   const { accessToken } = useAuthorizationCache();
+  const domain = import.meta.env.VITE_BACKEND_URL;
 
   function GetUserInfo(): Promise<User> {
     return new Promise<User>((resolve, reject) => {
-      fetch(`https://${domain}/user/`, {
+      fetch(`${domain}/user/`, {
         method: 'GET',
         credentials: 'same-origin',
         headers: new Headers({
diff --git a/libs/shared/lib/data-access/authorization/useAuth.jsx b/libs/shared/lib/data-access/authorization/useAuth.jsx
index 644abac87..ab8bb0a74 100644
--- a/libs/shared/lib/data-access/authorization/useAuth.jsx
+++ b/libs/shared/lib/data-access/authorization/useAuth.jsx
@@ -6,13 +6,9 @@ import { authorized } from '../store/authSlice';
 
 export const useAuth = () => {
   const keycloak = new Keycloak({
-    // url: import.meta.env.VITE_KEYCLOAK_URL,
-    // realm: import.meta.env.VITE_KEYCLOAK_REALM,
-    // clientId: import.meta.env.VITE_KEYCLOAK_CLIENT,
-    //TODO: remove this hardcode
-    url: 'https://keycloak.graphpolaris.com',
-    realm: 'graphpolaris',
-    clientId: 'web',
+    url: import.meta.env.VITE_KEYCLOAK_URL || 'https://keycloak.graphpolaris.com',
+    realm: import.meta.env.VITE_KEYCLOAK_REALM || 'graphpolaris',
+    clientId: import.meta.env.VITE_KEYCLOAK_CLIENT || 'web',
   });
 
   const isRun = useRef(false);
@@ -20,8 +16,21 @@ export const useAuth = () => {
   const dispatch = useAppDispatch();
 
   useEffect(() => {
-    if (isRun.current) return;
+    if (import.meta.env.VITE_SKIP_LOGIN) {
+      console.log('skipping login');
+      setLogin(true);
+      dispatch(
+        authorized({
+          userID: 'userID',
+          sessionID: 'sessionID',
+          accessToken: 'accessToken',
+          authorized: true,
+        })
+      );
+      return;
+    }
 
+    if (isRun.current) return;
     isRun.current = true;
     keycloak
       .init({
@@ -32,11 +41,9 @@ export const useAuth = () => {
       .then(async (isAuthenticated) => {
         // console.log("useAuth useEffect", isAuthenticated, keycloak.idTokenParsed);
         setLogin(isAuthenticated);
-
         // just for example here:
         // const profile = await getUserProfile();
         // console.log("useAuth useEffect profile", profile);
-
         await dispatch(
           authorized({
             // Info from https://www.keycloak.org/docs/latest/securing_apps/
diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx
index ec7e4496e..bca2d0453 100644
--- a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx
+++ b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx
@@ -16,7 +16,7 @@ export class WebSocketHandler implements BackendMessageReceiver {
 
   /** @param domain The domain to make the websocket connection with. */
   public constructor(domain: string) {
-    this.url = 'wss://' + domain + '/socket/';
+    this.url = domain;
     this.connected = false;
   }
 
diff --git a/libs/shared/lib/data-access/store/authSlice.ts b/libs/shared/lib/data-access/store/authSlice.ts
index 088359644..9c892fc46 100644
--- a/libs/shared/lib/data-access/store/authSlice.ts
+++ b/libs/shared/lib/data-access/store/authSlice.ts
@@ -1,6 +1,12 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 import type { RootState } from './store';
-import { useIsAuthorizedState } from '../authorization';
+
+type useIsAuthorizedState = {
+  authorized: boolean | undefined;
+  accessToken: string | undefined;
+  sessionID: string | undefined;
+  userID: string | undefined;
+};
 
 // Define the initial state using that type
 export const initialState: useIsAuthorizedState = {
@@ -16,21 +22,25 @@ export const authSlice = createSlice({
   initialState,
   reducers: {
     updateAccessToken(state, action: PayloadAction<string>) {
+      console.debug('Updating access token');
       state.accessToken = action.payload;
     },
     authorized(state, action: PayloadAction<useIsAuthorizedState>) {
+      console.debug('Authorized');
       state.authorized = action.payload.authorized;
       state.accessToken = action.payload.accessToken;
       state.sessionID = action.payload.sessionID;
       state.userID = action.payload.userID;
     },
     logout(state) {
+      console.debug('Logging out');
       state.authorized = undefined;
       state.accessToken = undefined;
       state.sessionID = undefined;
       state.userID = undefined;
     },
     unauthorized(state) {
+      console.debug('Unauthorized');
       state.authorized = false;
     },
   },
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.module.scss b/libs/shared/lib/querybuilder/panel/querybuilder.module.scss
index 1806d4850..aa0cb3737 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.module.scss
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.module.scss
@@ -1,36 +1,39 @@
-.reactflow {
-  width: 100%;
-  // height: 500px;
-
-  // :global(.react-flow__edges) {
-  //   z-index: 4 !important;
-  // }
-}
-
-//controls
-.controls {
-  left: auto !important;
-  bottom: auto !important;
-  top: 10px;
-  right: 20px;
-  width: auto !important;
-}
-.buttons {
-  left: auto !important;
-  bottom: auto !important;
-  top: 10px;
-  right: 20px;
-  & svg {
-    transform: scale(1.4);
-  }
-}
-
-.menuText {
-  font-size: small;
-  font-family: Poppins, sans-serif;
-}
-
-.full {
-  height: 100%;
-  width: 100%;
-}
+.reactflow {
+  width: 100%;
+  // height: 500px;
+
+  // :global(.react-flow__edges) {
+  //   z-index: 4 !important;
+  // }
+}
+
+//controls
+.controls {
+  left: auto !important;
+  bottom: auto !important;
+  top: 10px;
+  right: 20px;
+  width: auto !important;
+}
+.buttons {
+  left: auto !important;
+  bottom: auto !important;
+  top: 10px;
+  right: 20px;
+  & svg {
+    transform: scale(1.4);
+  }
+}
+
+.dndnode {
+}
+
+.menuText {
+  font-size: small;
+  font-family: Poppins, sans-serif;
+}
+
+.full {
+  height: 100%;
+  width: 100%;
+}
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
index fa3d2c51f..98d6cf335 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
@@ -30,7 +30,7 @@ import ReactFlow, {
 } from 'reactflow';
 import styles from './querybuilder.module.scss';
 
-import React, { ReactComponentElement, useMemo, useRef, useEffect, useCallback, useState } from 'react';
+import React, { ReactComponentElement, useMemo, useRef, useEffect, useCallback, useState, DragEventHandler } from 'react';
 import { AttributePill, ConnectionDragLine, ConnectionLine, EntityFlowElement, RelationPill } from '../pills';
 import { dragPillStarted, dragPillStopped, movePillTo } from '../pills/dragging/dragPill';
 import { Settings as SettingsIcon, Delete as DeleteIcon, ImportExport as ExportIcon } from '@mui/icons-material';
@@ -554,7 +554,6 @@ function PopupMenu(props: {
       ))}
     </Grid>
   );
-
   return (
     <Dialog onClose={handleClose} open={props.open} PaperComponent={PaperComponent}>
       <DialogTitle>Add New Node</DialogTitle>
@@ -570,14 +569,54 @@ function PopupMenu(props: {
   );
 }
 
+export const QueryBuilderPills = (props: { onClose: () => void; onClick: (value: AllLogicDescriptions, type: InputNodeType) => void }) => {
+  const onDragStart = (event: React.DragEvent, nodeType: InputNodeType) => {
+    console.log('drag start', nodeType);
+
+    event.dataTransfer.setData('application/reactflow', nodeType);
+    event.dataTransfer.effectAllowed = 'move';
+  };
+
+  const generateList = (list: AllLogicDescriptions[], type: InputNodeType) => (
+    <List>
+      {list.map((f, i) => (
+        <ListItem key={JSON.stringify(f) + type + i}>
+          {/* <ListItemAvatar>
+                <Avatar sx={{ bgcolor: blue[100], color: blue[600] }}>
+                  <PersonIcon />
+                </Avatar>
+              </ListItemAvatar> */}
+
+          <ListItemText
+            draggable
+            primary={f.name}
+            secondary={f.description}
+            onDragStart={(event) => onDragStart(event, type)}
+            key={f + type}
+          />
+        </ListItem>
+      ))}
+    </List>
+  );
+
+  return (
+    <aside>
+      <div>{generateList(MathFunctions, 'string')}</div>
+    </aside>
+  );
+};
+
 export const QueryBuilder = () => {
   return (
     <div
       style={{
         width: '100%',
         height: '100%',
+        display: 'flex',
+        gap: '1rem',
       }}
     >
+      <QueryBuilderPills />
       <ReactFlowProvider>
         <QueryBuilderInner />
       </ReactFlowProvider>
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
index 04e972f9c..fbac410a0 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
@@ -15,6 +15,7 @@ import { useQuerybuilderGraph } from '@graphpolaris/shared/lib/data-access';
 export const EntityFlowElement = React.memo((node: SchemaReactflowEntityNode) => {
   const theme = useTheme();
   const data = node.data;
+  const forceOpen: boolean = true;
 
   const graph = useQuerybuilderGraph();
   const myEdges = graph.edges.filter(
@@ -101,10 +102,11 @@ export const EntityFlowElement = React.memo((node: SchemaReactflowEntityNode) =>
         <span className={styles.entitySpan}>{data.name}</span>
       </div> */}
       {data?.attributes && (
-        <div className={styles.content + ' ' + (showingDropdown ? styles.content_display : '')}>
+        <div className={styles.content + ' ' + (showingDropdown || forceOpen ? styles.content_display : '')}>
           {data.attributes
             .filter(
               (attribute, i) =>
+                forceOpen ||
                 hovered ||
                 handleBeingDragged === i ||
                 myEdges.some((edge) => edge?.attributes?.sourceHandle === getHandleIdFromReactflow(node, attribute))
diff --git a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx b/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
index aad5bef4a..1bdbb7dba 100644
--- a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
+++ b/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
@@ -111,56 +111,56 @@ export const EntityNode = React.memo(({ id, data }: NodeProps<SchemaReactflowNod
         id="entitySourceLeft"
         position={Position.Left}
         type="source"
-        hidden={Array.from(data.handles).includes('entitySourceLeft') ? false : true}
+        // hidden={Array.from(data.handles).includes('entitySourceLeft') ? false : true}
       ></Handle>
       <Handle
         style={{ pointerEvents: 'none' }}
         id="entityTargetLeft"
         position={Position.Left}
         type="target"
-        hidden={Array.from(data.handles).includes('entityTargetLeft') ? false : true}
+        // hidden={Array.from(data.handles).includes('entityTargetLeft') ? false : true}
       ></Handle>
       <Handle
         style={{ pointerEvents: 'none' }}
         id="entitySourceRight"
         position={Position.Right}
         type="source"
-        hidden={Array.from(data.handles).includes('entitySourceRight') ? false : true}
+        // hidden={Array.from(data.handles).includes('entitySourceRight') ? false : true}
       ></Handle>
       <Handle
         style={{ pointerEvents: 'none' }}
         id="entityTargetRight"
         position={Position.Right}
         type="target"
-        hidden={Array.from(data.handles).includes('entityTargetRight') ? false : true}
+        // hidden={Array.from(data.handles).includes('entityTargetRight') ? false : true}
       ></Handle>
       <Handle
         style={{ pointerEvents: 'none' }}
         id="entitySourceTop"
         position={Position.Top}
         type="source"
-        hidden={Array.from(data.handles).includes('entitySourceTop') ? false : true}
+        // hidden={Array.from(data.handles).includes('entitySourceTop') ? false : true}
       ></Handle>
       <Handle
         style={{ pointerEvents: 'none' }}
         id="entityTargetTop"
         position={Position.Top}
         type="target"
-        hidden={Array.from(data.handles).includes('entityTargetTop') ? false : true}
+        // hidden={Array.from(data.handles).includes('entityTargetTop') ? false : true}
       ></Handle>
       <Handle
         style={{ pointerEvents: 'none' }}
         id="entitySourceBottom"
         position={Position.Bottom}
         type="source"
-        hidden={Array.from(data.handles).includes('entitySourceBottom') ? false : true}
+        // hidden={Array.from(data.handles).includes('entitySourceBottom') ? false : true}
       ></Handle>
       <Handle
         style={{ pointerEvents: 'none' }}
         id="entityTargetBottom"
         position={Position.Bottom}
         type="target"
-        hidden={Array.from(data.handles).includes('entityTargetBottom') ? false : true}
+        // hidden={Array.from(data.handles).includes('entityTargetBottom') ? false : true}
       ></Handle>
       <div className={styles.nodeWrapper}>
         <span className={styles.nodeData} style={{ color: theme.palette.custom.elementText }}>
diff --git a/libs/shared/node.d.ts b/libs/shared/node.d.ts
new file mode 100644
index 000000000..0cb37864e
--- /dev/null
+++ b/libs/shared/node.d.ts
@@ -0,0 +1,12 @@
+interface ImportMeta {
+  env: {
+    VITE_BACKEND_URL: string;
+    VITE_BACKEND_USER: string;
+    VITE_BACKEND_SCHEMA: string;
+    VITE_BACKEND_QUERY: string;
+    VITE_STAGING: string;
+    VITE_KEYCLOAK_URL: string;
+    VITE_KEYCLOAK_REALM: string;
+    VITE_KEYCLOAK_CLIENT: string;
+  };
+}
diff --git a/turbo.json b/turbo.json
index 4ce7ad057..131b3025a 100644
--- a/turbo.json
+++ b/turbo.json
@@ -1,20 +1,20 @@
-{
-  "$schema": "https://turbo.build/schema.json",
-  "globalDependencies": ["**/.env.*local"],
-  "pipeline": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**", ".next/**"]
-    },
-    "lint": {
-      "outputs": []
-    },
-    "test": {},
-    "dev": {
-      "cache": false
-    },
-    "sb": {
-      "outputs": ["storybook-static/**"]
-    }
-  }
-}
+{
+  "$schema": "https://turbo.build/schema.json",
+  "globalDependencies": ["**/.env*"],
+  "pipeline": {
+    "build": {
+      "dependsOn": ["^build"],
+      "outputs": ["dist/**", ".next/**"]
+    },
+    "lint": {
+      "outputs": []
+    },
+    "test": {},
+    "dev": {
+      "cache": false
+    },
+    "sb": {
+      "outputs": ["storybook-static/**"]
+    }
+  }
+}
-- 
GitLab