Skip to content

Commit 4285943

Browse files
committed
include session id so we can process data in the python server
1 parent 1b61337 commit 4285943

File tree

6 files changed

+114
-13
lines changed

6 files changed

+114
-13
lines changed

.env.template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# Provide frontend configuration settings from environment variables
2-
SHOW_KEYS_ENABLED=true
2+
SHOW_KEYS_ENABLED=true
3+
SESSION_ENABLED=true # if true, the session id will be used to track the user's session

py-src/data_formulator/app.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,14 @@
1010
mimetypes.add_type('application/javascript', '.mjs')
1111

1212
import flask
13-
from flask import Flask, request, send_from_directory, redirect, url_for
13+
from flask import Flask, request, send_from_directory, redirect, url_for, session
1414
from flask import stream_with_context, Response
1515
import html
1616
import pandas as pd
1717

1818
import webbrowser
1919
import threading
2020

21-
from flask_cors import CORS
22-
2321
import logging
2422

2523
import json
@@ -40,13 +38,14 @@
4038
from data_formulator.agents.client_utils import Client
4139

4240
from dotenv import load_dotenv
41+
import secrets
4342

4443
APP_ROOT = Path(os.path.join(Path(__file__).parent)).absolute()
4544

4645
import os
4746

4847
app = Flask(__name__, static_url_path='', static_folder=os.path.join(APP_ROOT, "dist"))
49-
CORS(app)
48+
app.secret_key = secrets.token_hex(16) # Generate a random secret key for sessions
5049

5150
print(APP_ROOT)
5251

@@ -75,7 +74,6 @@
7574
app.logger.info("Flask specific log") # Web request related logging
7675

7776

78-
7977
def get_client(model_config):
8078
for key in model_config:
8179
model_config[key] = model_config[key].strip()
@@ -540,11 +538,41 @@ def request_code_expl():
540538
expl = ""
541539
return expl
542540

541+
@app.route('/get-session-id', methods=['GET'])
542+
def get_session_id():
543+
"""Endpoint to get or confirm a session ID from the client"""
544+
545+
if not os.getenv("SESSION_ENABLED", "true").lower() == "true":
546+
return flask.jsonify({
547+
"status": "ok",
548+
"session_id": None
549+
})
550+
551+
# Create session if it doesn't exist
552+
if 'session_id' not in session:
553+
session['session_id'] = secrets.token_hex(16)
554+
session.permanent = True
555+
logger.info(f"Created new session: {session['session_id']}")
556+
557+
return flask.jsonify({
558+
"status": "ok",
559+
"session_id": session['session_id']
560+
})
561+
543562
@app.route('/app-config', methods=['GET'])
544563
def get_app_config():
545564
"""Provide frontend configuration settings from environment variables"""
565+
566+
# Create session if it doesn't exist
567+
if 'session_id' not in session:
568+
session['session_id'] = secrets.token_hex(16)
569+
session.permanent = True
570+
logger.info(f"Created new session: {session['session_id']}")
571+
546572
config = {
547-
"SHOW_KEYS_ENABLED": os.getenv("SHOW_KEYS_ENABLED", "true").lower() == "true"
573+
"SHOW_KEYS_ENABLED": os.getenv("SHOW_KEYS_ENABLED", "true").lower() == "true",
574+
"SESSION_ENABLED": os.getenv("SESSION_ENABLED", "true").lower() == "true",
575+
"SESSION_ID": session['session_id'] if os.getenv("SESSION_ENABLED", "true").lower() == "true" else None
548576
}
549577
return flask.jsonify(config)
550578

src/app/App.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
dfActions,
1111
fetchAvailableModels,
1212
fetchFieldSemanticType,
13+
fetchSessionId
1314
} from './dfSlice'
1415

1516
import blue from '@mui/material/colors/blue';
@@ -226,6 +227,7 @@ const TableMenu: React.FC = () => {
226227
const SessionMenu: React.FC = () => {
227228
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
228229
const open = Boolean(anchorEl);
230+
const sessionId = useSelector((state: DataFormulatorState) => state.sessionId);
229231

230232
return (
231233
<>
@@ -235,7 +237,7 @@ const SessionMenu: React.FC = () => {
235237
endIcon={<KeyboardArrowDownIcon />}
236238
sx={{ textTransform: 'none' }}
237239
>
238-
Session
240+
Session {sessionId ? `(${sessionId.substring(0, 8)}...)` : ''}
239241
</Button>
240242
<Menu
241243
id="session-menu"
@@ -248,6 +250,13 @@ const SessionMenu: React.FC = () => {
248250
}}
249251
sx={{ '& .MuiMenuItem-root': { padding: 0, margin: 0 } }}
250252
>
253+
{sessionId && (
254+
<MenuItem disabled>
255+
<Typography sx={{ fontSize: 12, color: 'text.secondary', mx: 2 }}>
256+
ID: {sessionId}
257+
</Typography>
258+
</MenuItem>
259+
)}
251260
<MenuItem onClick={() => {}}>
252261
<ExportStateButton />
253262
</MenuItem>
@@ -413,6 +422,7 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
413422
const visViewMode = useSelector((state: DataFormulatorState) => state.visViewMode);
414423
const config = useSelector((state: DataFormulatorState) => state.config);
415424
const tables = useSelector((state: DataFormulatorState) => state.tables);
425+
const sessionId = useSelector((state: DataFormulatorState) => state.sessionId);
416426

417427
// if the user has logged in
418428
const [userInfo, setUserInfo] = useState<{ name: string, userId: string } | undefined>(undefined);
@@ -462,6 +472,7 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
462472
useEffect(() => {
463473
document.title = toolName;
464474
dispatch(fetchAvailableModels());
475+
dispatch(fetchSessionId());
465476
}, []);
466477

467478
let theme = createTheme({

src/app/dfSlice.tsx

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export interface ModelConfig {
3737

3838
// Define a type for the slice state
3939
export interface DataFormulatorState {
40-
40+
sessionId: string | undefined;
4141
models: ModelConfig[];
4242
selectedModelId: string | undefined;
4343
testedModels: {id: string, status: 'ok' | 'error' | 'testing' | 'unknown', message: string}[];
@@ -73,7 +73,7 @@ export interface DataFormulatorState {
7373

7474
// Define the initial state using that type
7575
const initialState: DataFormulatorState = {
76-
76+
sessionId: undefined,
7777
models: [],
7878
selectedModelId: undefined,
7979
testedModels: [],
@@ -227,6 +227,47 @@ export const fetchAvailableModels = createAsyncThunk(
227227
return response.json();
228228
}
229229
);
230+
231+
// Add this helper function to generate a UUID
232+
const generateSessionId = () => {
233+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
234+
const r = Math.random() * 16 | 0,
235+
v = c == 'x' ? r : (r & 0x3 | 0x8);
236+
return v.toString(16);
237+
});
238+
};
239+
240+
// Create or retrieve a session ID from local storage
241+
const getOrCreateSessionId = () => {
242+
let sessionId = localStorage.getItem('app_session_id');
243+
if (!sessionId) {
244+
sessionId = generateSessionId();
245+
localStorage.setItem('app_session_id', sessionId);
246+
}
247+
return sessionId;
248+
};
249+
250+
// Update your fetch function to include the session ID in the request
251+
export const fetchSessionId = createAsyncThunk(
252+
"dataFormulatorSlice/fetchSessionId",
253+
async () => {
254+
const sessionId = getOrCreateSessionId();
255+
256+
const response = await fetch(getUrls().SESSION_ID, {
257+
method: 'GET',
258+
headers: {
259+
'Content-Type': 'application/json',
260+
'X-Session-ID': sessionId // Pass session ID as a custom header
261+
}
262+
});
263+
264+
const data = await response.json();
265+
return {
266+
...data,
267+
session_id: sessionId // Ensure we return the session ID
268+
};
269+
}
270+
);
230271

231272
export const dataFormulatorSlice = createSlice({
232273
name: 'dataFormulatorSlice',
@@ -632,7 +673,10 @@ export const dataFormulatorSlice = createSlice({
632673
} else {
633674
state.chartSynthesisInProgress = state.chartSynthesisInProgress.filter(s => s != action.payload.chartId);
634675
}
635-
}
676+
},
677+
setSessionId: (state, action: PayloadAction<string>) => {
678+
state.sessionId = action.payload;
679+
},
636680
},
637681
extraReducers: (builder) => {
638682
builder
@@ -686,6 +730,11 @@ export const dataFormulatorSlice = createSlice({
686730
console.log("fetched codeExpl");
687731
console.log(action.payload);
688732
})
733+
.addCase(fetchSessionId.fulfilled, (state, action) => {
734+
console.log(">>> fetchSessionId <<<")
735+
console.log(action.payload)
736+
state.sessionId = action.payload.session_id;
737+
})
689738
},
690739
})
691740

src/app/utils.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface PopupConfig {
2020
}
2121

2222
export const appConfig: AppConfig = {
23-
serverUrl: process.env.NODE_ENV == "production" ? "./" : "http://127.0.0.1:5000/",
23+
serverUrl: process.env.NODE_ENV == "production" ? "./" : "/api",
2424
};
2525

2626
export function assignAppConfig(config: AppConfig) {
@@ -51,7 +51,9 @@ export function getUrls() {
5151

5252
APP_CONFIG: `${appConfig.serverUrl}/app-config`,
5353

54-
AUTH_INFO_PREFIX: `${appConfig.serverUrl}/.auth/`
54+
AUTH_INFO_PREFIX: `${appConfig.serverUrl}/.auth/`,
55+
56+
SESSION_ID: `${appConfig.serverUrl}/get-session-id`,
5557
};
5658
}
5759

vite.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,14 @@ export default defineConfig({
1919
}
2020
}
2121
},
22+
server: {
23+
proxy: {
24+
'/api': {
25+
target: 'http://localhost:5000',
26+
changeOrigin: true,
27+
// Optional: rewrite paths
28+
rewrite: (path) => path.replace(/^\/api/, '')
29+
}
30+
}
31+
}
2232
});

0 commit comments

Comments
 (0)