Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Final Pre-Demo Errors Resolved #186

Merged
merged 4 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading