-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Final Pre-Demo Errors Resolved (#186)
* repaired monaco instance and implementations. expanded validation system to use a table system similar to the postvalidation system, and added caching via swr and memoization to limit re-renders * forgot to add msv reloads after upload, etc. * remaining minor issues resolved
- Loading branch information
1 parent
059d4f9
commit 8fe6ad4
Showing
14 changed files
with
10,604 additions
and
11,615 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
167 changes: 66 additions & 101 deletions
167
frontend/app/(hub)/measurementshub/validations/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,133 +1,98 @@ | ||
'use client'; | ||
|
||
import { Box, Card, CardContent, Typography } from '@mui/joy'; | ||
import React, { useEffect, useState } from 'react'; | ||
import ValidationCard from '@/components/validationcard'; | ||
import React, { useEffect, useState, useMemo } from 'react'; | ||
import useSWR from 'swr'; | ||
import { ValidationProceduresRDS } from '@/config/sqlrdsdefinitions/validations'; | ||
import { useSiteContext } from '@/app/contexts/userselectionprovider'; | ||
import { useOrgCensusContext, usePlotContext, useSiteContext } from '@/app/contexts/userselectionprovider'; | ||
import { useSession } from 'next-auth/react'; | ||
import { useTheme } from '@mui/joy'; | ||
import dynamic from 'next/dynamic'; | ||
import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; | ||
|
||
const fetcher = (url: string) => fetch(url).then(res => res.json()); | ||
|
||
export default function ValidationsPage() { | ||
const [globalValidations, setGlobalValidations] = React.useState<ValidationProceduresRDS[]>([]); | ||
const [loading, setLoading] = useState<boolean>(true); // Use a loading state instead of refresh | ||
const [schemaDetails, setSchemaDetails] = useState<{ table_name: string; column_name: string }[]>([]); | ||
const { data: session } = useSession(); | ||
|
||
const currentSite = useSiteContext(); | ||
const currentPlot = usePlotContext(); | ||
const currentCensus = useOrgCensusContext(); | ||
const theme = useTheme(); | ||
const isDarkMode = theme.palette.mode === 'dark'; | ||
|
||
const { data: globalValidations, mutate: updateValidations } = useSWR<ValidationProceduresRDS[]>('/api/validations/crud', fetcher); | ||
|
||
const { data: schemaData } = useSWR<{ schema: { table_name: string; column_name: string }[] }>( | ||
currentSite?.schemaName ? `/api/structure/${currentSite.schemaName}` : null, | ||
fetcher | ||
); | ||
|
||
const replacements = useMemo( | ||
() => ({ | ||
schema: currentSite?.schemaName, | ||
currentPlotID: currentPlot?.plotID, | ||
currentCensusID: currentCensus?.dateRanges[0].censusID | ||
}), | ||
[currentSite?.schemaName, currentPlot?.plotID, currentCensus?.dateRanges] | ||
); | ||
|
||
const ValidationRow = dynamic(() => import('@/components/validationrow'), { ssr: false }); | ||
|
||
const [expandedValidationID, setExpandedValidationID] = useState<number | null>(null); | ||
|
||
useEffect(() => { | ||
if (session !== null && !['db admin', 'global'].includes(session.user.userStatus)) { | ||
if (session && !['db admin', 'global'].includes(session.user.userStatus)) { | ||
throw new Error('access-denied'); | ||
} | ||
}, []); | ||
}, [session]); | ||
|
||
const handleSaveChanges = async (updatedValidation: ValidationProceduresRDS) => { | ||
try { | ||
// Make the API call to toggle the validation | ||
const response = await fetch(`/api/validations/crud`, { | ||
method: 'PATCH', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify(updatedValidation) // Pass the entire updated validation object | ||
body: JSON.stringify(updatedValidation) | ||
}); | ||
if (response.ok) { | ||
// Update the globalValidations state directly | ||
setGlobalValidations(prev => prev.map(val => (val.validationID === updatedValidation.validationID ? updatedValidation : val))); | ||
updateValidations(prev => (prev ? prev.map(val => (val.validationID === updatedValidation.validationID ? updatedValidation : val)) : [])); | ||
} else { | ||
console.error('Failed to toggle validation'); | ||
console.error('Failed to update validation'); | ||
} | ||
} catch (error) { | ||
console.error('Error toggling validation:', error); | ||
console.error('Error updating validation:', error); | ||
} | ||
}; | ||
|
||
const handleDelete = async (validationID?: number) => { | ||
try { | ||
// Make the API call to delete the validation | ||
const response = await fetch(`/api/validations/delete/${validationID}`, { | ||
method: 'DELETE' | ||
}); | ||
if (response.ok) { | ||
// Remove the deleted validation from the globalValidations state | ||
setGlobalValidations(prev => prev.filter(validation => validation.validationID !== validationID)); | ||
} else { | ||
console.error('Failed to delete validation'); | ||
} | ||
} catch (error) { | ||
console.error('Error deleting validation:', error); | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
async function fetchValidations() { | ||
try { | ||
const response = await fetch('/api/validations/crud', { method: 'GET' }); | ||
const data = await response.json(); | ||
setGlobalValidations(data); | ||
} catch (err) { | ||
console.error('Error fetching validations:', err); | ||
} finally { | ||
setLoading(false); // Loading is complete | ||
} | ||
} | ||
|
||
fetchValidations().catch(console.error); // Initial load | ||
}, []); | ||
|
||
useEffect(() => { | ||
if (typeof window !== 'undefined') { | ||
// Set up Monaco Editor worker path | ||
window.MonacoEnvironment = { | ||
getWorkerUrl: function () { | ||
return '_next/static/[name].worker.js'; | ||
} | ||
}; | ||
} | ||
}, []); | ||
|
||
// Fetch schema details when component mounts | ||
useEffect(() => { | ||
const fetchSchema = async () => { | ||
try { | ||
const response = await fetch(`/api/structure/${currentSite?.schemaName ?? ''}`); | ||
const data = await response.json(); | ||
if (data.schema) { | ||
setSchemaDetails(data.schema); | ||
} | ||
} catch (error) { | ||
console.error('Error fetching schema:', error); | ||
} | ||
}; | ||
|
||
if (currentSite?.schemaName) { | ||
fetchSchema().then(r => console.log(r)); | ||
} | ||
}, [currentSite?.schemaName]); | ||
function handleToggleClick(incomingValidationID: number) { | ||
setExpandedValidationID(prev => (prev === incomingValidationID ? null : incomingValidationID)); | ||
} | ||
|
||
return ( | ||
<Box sx={{ width: '100%' }}> | ||
<Card variant={'plain'} sx={{ width: '100%' }}> | ||
<CardContent> | ||
<Typography level={'title-lg'} fontWeight={'bold'}> | ||
Review Global Validations | ||
</Typography> | ||
{globalValidations.map(validation => ( | ||
<ValidationCard | ||
onDelete={handleDelete} | ||
onSaveChanges={handleSaveChanges} | ||
<TableContainer component={Paper}> | ||
<Table stickyHeader sx={{ tableLayout: 'fixed', width: '100%' }}> | ||
<TableHead> | ||
<TableRow> | ||
<TableCell sx={{ width: '5%' }}>Enabled?</TableCell> | ||
<TableCell sx={{ width: '10%' }}>Validation</TableCell> | ||
<TableCell sx={{ width: '15%' }}>Description</TableCell> | ||
<TableCell sx={{ width: '10%' }}>Affecting Criteria</TableCell> | ||
<TableCell sx={{ flexGrow: 1, flexShrink: 0, flexBasis: '35%' }}>Query</TableCell> | ||
<TableCell sx={{ width: '10%' }}>Actions</TableCell> | ||
</TableRow> | ||
</TableHead> | ||
<TableBody> | ||
{globalValidations?.map((validation, index) => ( | ||
<ValidationRow | ||
key={index} | ||
validation={validation} | ||
key={validation.validationID} | ||
schemaDetails={schemaDetails} | ||
onSaveChanges={handleSaveChanges} | ||
schemaDetails={schemaData?.schema || []} | ||
expandedValidationID={expandedValidationID} | ||
handleExpandClick={() => handleToggleClick(validation.validationID!)} | ||
isDarkMode={isDarkMode} | ||
replacements={replacements} | ||
/> | ||
))} | ||
</CardContent> | ||
</Card> | ||
<Card variant={'plain'} sx={{ width: '100%' }}> | ||
<CardContent> | ||
<Typography level={'title-lg'} fontWeight={'bold'}> | ||
Review Site-Specific Validations | ||
</Typography> | ||
</CardContent> | ||
</Card> | ||
</Box> | ||
</TableBody> | ||
</Table> | ||
</TableContainer> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
'use client'; | ||
|
||
import { useMonaco } from '@monaco-editor/react'; | ||
import dynamic from 'next/dynamic'; | ||
import React, { Dispatch, memo, SetStateAction, useEffect } from 'react'; | ||
|
||
const Editor = dynamic(() => import('@monaco-editor/react'), { ssr: false }); | ||
|
||
type CustomMonacoEditorProps = { | ||
schemaDetails: { | ||
table_name: string; | ||
column_name: string; | ||
}[]; | ||
setContent?: Dispatch<SetStateAction<string | undefined>>; | ||
content?: string; | ||
height?: any; | ||
isDarkMode?: boolean; | ||
} & React.ComponentPropsWithoutRef<typeof Editor>; | ||
|
||
function CustomMonacoEditor(broadProps: CustomMonacoEditorProps) { | ||
const { schemaDetails, setContent = () => {}, content, height, options = {}, isDarkMode, ...props } = broadProps; | ||
const monaco = useMonaco(); | ||
|
||
useEffect(() => { | ||
if (monaco) { | ||
monaco.languages.registerCompletionItemProvider('mysql', { | ||
provideCompletionItems: (model, position) => { | ||
const suggestions: any[] = []; | ||
const word = model.getWordUntilPosition(position); | ||
const range = new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); | ||
|
||
const tables = Array.from(new Set(schemaDetails.map(row => row.table_name))); | ||
tables.forEach(table => { | ||
suggestions.push({ | ||
label: table, | ||
kind: monaco.languages.CompletionItemKind.Function, | ||
insertText: table, | ||
detail: 'Table', | ||
range | ||
}); | ||
}); | ||
|
||
schemaDetails.forEach(({ table_name, column_name }) => { | ||
suggestions.push({ | ||
label: `${table_name}.${column_name}`, | ||
kind: monaco.languages.CompletionItemKind.Property, | ||
insertText: `${table_name}.${column_name}`, | ||
detail: `Column from ${table_name}`, | ||
range | ||
}); | ||
}); | ||
|
||
return { suggestions }; | ||
} | ||
}); | ||
} | ||
}, [monaco]); | ||
|
||
return ( | ||
<Editor | ||
height={height ?? '60vh'} | ||
language="mysql" | ||
value={content} | ||
onChange={value => setContent(value ?? '')} | ||
theme={isDarkMode ? 'vs-dark' : 'light'} | ||
options={{ | ||
...options, // Spread the existing options | ||
readOnly: options.readOnly ?? false // Ensure readOnly is explicitly respected | ||
}} | ||
{...props} | ||
/> | ||
); | ||
} | ||
|
||
export default memo(CustomMonacoEditor); |
Oops, something went wrong.