Skip to content

Commit

Permalink
Final Pre-Demo Errors Resolved (#186)
Browse files Browse the repository at this point in the history
* 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
siddheshraze authored Dec 6, 2024
1 parent 059d4f9 commit 8fe6ad4
Show file tree
Hide file tree
Showing 14 changed files with 10,604 additions and 11,615 deletions.
25 changes: 24 additions & 1 deletion frontend/app/(hub)/measurementshub/postvalidation/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useOrgCensusContext, usePlotContext, useSiteContext } from '@/app/conte
import React, { useEffect, useState } from 'react';
import { Box, Button, Checkbox, Table, Typography, useTheme } from '@mui/joy';
import { PostValidationQueriesRDS } from '@/config/sqlrdsdefinitions/validations';
import PostValidationRow from '@/components/client/postvalidationrow';
import { Paper, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
import { Done } from '@mui/icons-material';
import { useLoading } from '@/app/contexts/loadingprovider';
import dynamic from 'next/dynamic';

export default function PostValidationPage() {
const currentSite = useSiteContext();
Expand All @@ -17,13 +17,16 @@ export default function PostValidationPage() {
const [expandedQuery, setExpandedQuery] = useState<number | null>(null);
const [expandedResults, setExpandedResults] = useState<number | null>(null);
const [selectedResults, setSelectedResults] = useState<PostValidationQueriesRDS[]>([]);
const [schemaDetails, setSchemaDetails] = useState<{ table_name: string; column_name: string }[]>([]);
const replacements = {
schema: currentSite?.schemaName,
currentPlotID: currentPlot?.plotID,
currentCensusID: currentCensus?.dateRanges[0].censusID
};
const { setLoading } = useLoading();

const PostValidationRow = dynamic(() => import('@/components/client/postvalidationrow'), { ssr: false });

const enabledPostValidations = postValidations.filter(query => query.isEnabled);
const disabledPostValidations = postValidations.filter(query => !query.isEnabled);

Expand Down Expand Up @@ -86,6 +89,24 @@ export default function PostValidationPage() {
.then(() => setLoading(false));
}, []);

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 (postValidations.length > 0) {
fetchSchema().then(r => console.log(r));
}
}, [postValidations]);

const handleExpandClick = (queryID: number) => {
setExpandedQuery(expandedQuery === queryID ? null : queryID);
};
Expand Down Expand Up @@ -218,6 +239,7 @@ export default function PostValidationPage() {
handleExpandClick={handleExpandClick}
handleExpandResultsClick={handleExpandResultsClick}
handleSelectResult={handleSelectResult}
schemaDetails={schemaDetails}
/>
))}

Expand All @@ -233,6 +255,7 @@ export default function PostValidationPage() {
handleExpandClick={handleExpandClick}
handleExpandResultsClick={handleExpandResultsClick}
handleSelectResult={handleSelectResult}
schemaDetails={schemaDetails}
/>
))}
</TableBody>
Expand Down
167 changes: 66 additions & 101 deletions frontend/app/(hub)/measurementshub/validations/page.tsx
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>
);
}
14 changes: 14 additions & 0 deletions frontend/app/api/sqlload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ export async function POST(request: NextRequest) {
}
}
}

// Update Census Start/End Dates
const combinedQuery = `
UPDATE ${schema}.census c
JOIN (
SELECT CensusID, MIN(MeasurementDate) AS FirstMeasurementDate, MAX(MeasurementDate) AS LastMeasurementDate
FROM ${schema}.coremeasurements
WHERE CensusID = ${censusID}
GROUP BY CensusID
) m ON c.CensusID = m.CensusID
SET c.StartDate = m.FirstMeasurementDate, c.EndDate = m.LastMeasurementDate
WHERE c.CensusID = ${censusID};`;

await connectionManager.executeQuery(combinedQuery);
await connectionManager.closeConnection();
return new NextResponse(JSON.stringify({ message: 'Insert to SQL successful', idToRows: idToRows }), { status: HTTPResponses.OK });
}
75 changes: 75 additions & 0 deletions frontend/components/client/custommonacoeditor.tsx
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);
Loading

0 comments on commit 8fe6ad4

Please sign in to comment.