2626
2727import React , { use , useEffect , useState } from 'react'
2828import { useDebounce } from 'use-debounce'
29- import lookmlParser from 'lookml-parser'
29+ import lookmlParser_parseFilesArrray from 'lookml-parser/lib/parse-files-array '
3030
3131import Button from '@mui/material/Button'
3232import Stack from '@mui/material/Stack'
@@ -44,28 +44,29 @@ import Typography from '@mui/material/Typography'
4444
4545const ProjectPage = ( props ) => {
4646 const {
47- initProjectFiles,
47+ projectFiles,
48+ setProjectFiles,
4849 setTab,
4950 setProject
5051 } = props
5152
5253 // Core state
53- const [ projectFiles , setProjectFiles ] = useState ( initProjectFiles ?? [ ] )
5454 const [ selectedFileContent , setSelectedFileContent ] = useState ( "" )
5555 const [ selectedFileIndex , setSelectedFileIndex ] = useState ( projectFiles . length ? 0 : undefined )
5656 const [ renaming , setRenaming ] = useState ( { index : null , isNew : false , path : '' } )
5757
5858 // Derived state
59- const [ debouncedSelectedFileContent ] = useDebounce ( selectedFileContent , 1000 )
59+ const [ debouncedSelectedFileContent ] = useDebounce ( selectedFileContent , 2000 )
6060 const [ projectStatus , setProjectStatus ] = useState ( "" )
61+ const [ parseErrors , setParseErrors ] = useState ( { } )
6162 const [ ctaDisabled , setCtaDisabled ] = useState ( true )
6263
6364 // Effects
64- useEffect ( updateSelectedFileContent , [ selectedFileIndex , projectFiles ] )
65+ useEffect ( updateSelectedFileContent , [ selectedFileIndex ] )
6566 useEffect ( updateProjectFile , [ debouncedSelectedFileContent ] )
6667 useEffect ( ( ) => {
6768 parseProject ( )
68- } , [ projectFiles ] )
69+ } , [ projectFiles ] ) ;
6970
7071 return (
7172 < Stack direction = "column" spacing = { 2 } className = "project-page" >
@@ -74,8 +75,8 @@ const ProjectPage = (props) => {
7475 < Stack direction = "row" justifyContent = "flex-end" alignItems = "center" spacing = { 2 } >
7576 < Typography > { projectStatus } </ Typography >
7677 < Button
77- variant = "contained"
78- onClick = { ( ) => setTab ( "rule" ) }
78+ variant = "contained"
79+ onClick = { handleInspectRulesClick }
7980 value = "rule"
8081 disabled = { ctaDisabled } >
8182 Inspect Rule(s)
@@ -92,6 +93,7 @@ const ProjectPage = (props) => {
9293 < ListItem
9394 key = { f }
9495 disablePadding
96+ sx = { { backgroundColor : parseErrors [ file . path ] ? 'rgba(255, 0, 0, 0.1)' : 'transparent' } }
9597 secondaryAction = { renaming . index !== f && (
9698 < Stack direction = "row" >
9799 < IconButton edge = "end" aria-label = "rename" onClick = { ( ) => handleRenameFile ( f ) } >
@@ -149,11 +151,23 @@ const ProjectPage = (props) => {
149151 onChange = { handleFileContentsChange }
150152 disabled = { selectedFileIndex === undefined }
151153 > </ TextField >
154+ { parseErrors [ projectFiles [ selectedFileIndex ] ?. path ] && (
155+ < Typography color = "error" variant = "caption" sx = { { mt : 1 } } >
156+ { trunc ( parseErrors [ projectFiles [ selectedFileIndex ] ?. path ] , 120 ) }
157+ </ Typography >
158+ ) }
152159 </ Stack >
153160 </ Stack >
154161 )
155162
163+ async function handleInspectRulesClick ( ) {
164+ updateProjectFile ( selectedFileContent )
165+ const success = await parseProject ( projectFiles )
166+ if ( success ) setTab ( "rule" )
167+ }
168+
156169 function handleFileSelect ( index ) {
170+ updateProjectFile ( selectedFileContent )
157171 setSelectedFileIndex ( index )
158172 }
159173
@@ -182,7 +196,7 @@ const ProjectPage = (props) => {
182196 setProjectFiles ( newFiles ) ;
183197 if ( isNew ) {
184198 setSelectedFileIndex ( index ) ;
185- }
199+ }
186200 setRenaming ( { index : null , isNew : false , path : '' } ) ;
187201 }
188202
@@ -197,6 +211,7 @@ const ProjectPage = (props) => {
197211 // Add a temporary placeholder file and enter renaming mode for it
198212 const newIndex = projectFiles . length ;
199213 setProjectFiles ( [ ...projectFiles , { path : '' , contents : '' } ] ) ;
214+ updateProjectFile ( selectedFileContent )
200215 setRenaming ( { index : newIndex , isNew : true , path : 'new_file.view.lkml' } ) ;
201216 }
202217
@@ -230,35 +245,57 @@ const ProjectPage = (props) => {
230245 }
231246
232247 function updateProjectFile ( ) {
248+ if ( selectedFileIndex === undefined ) return ;
249+ updateProjectFile ( debouncedSelectedFileContent ) ;
250+ }
251+
252+ function updateProjectFile ( content ) {
233253 if ( selectedFileIndex === undefined ) return ;
234254 const currentFile = projectFiles [ selectedFileIndex ] ;
235- if ( ! currentFile || currentFile . contents === debouncedSelectedFileContent ) return ;
255+ if ( ! currentFile || currentFile . contents === content ) return ;
236256
237257 const newFiles = [ ...projectFiles ] ;
238- newFiles [ selectedFileIndex ] = { ...currentFile , contents : debouncedSelectedFileContent } ;
239- setProjectFiles ( newFiles ) ;
258+ newFiles [ selectedFileIndex ] = { ...currentFile , contents : content } ;
259+ setProjectFiles ( newFiles )
240260 }
241261
242- async function parseProject ( ) {
243- if ( ! projectFiles || projectFiles . length === 0 ) {
262+ async function parseProject ( files = projectFiles ) {
263+ if ( ! files || files . length === 0 ) {
244264 setProjectStatus ( "No files in project" )
245265 setProject ( undefined )
246266 setCtaDisabled ( true )
247- return
267+ return false ;
248268 }
269+
249270 try {
250271 setProjectStatus ( "Parsing LookML..." )
251- const parsedProject = await lookmlParser . parseFiles ( { source : projectFiles } )
252- // CONTINUE HERE ^ Need to go update LookML parser to work better in browser environments
253- // - Make sure that references to glob are conditional/isolated.
254- // - Don't rely on fs/path to load PEG code, use import/require insteat
255- setProject ( parsedProject )
256- setProjectStatus ( "✅ Project ready" )
257- setCtaDisabled ( false )
272+ const parsedProject = await lookmlParser_parseFilesArrray (
273+ files . map ( f => ( { path :f . path , read : ( ) => f . contents } ) )
274+ )
275+ if ( parsedProject . errors && parsedProject . errors . length > 0 ) {
276+ const firstError = parsedProject . errors [ 0 ] ;
277+ setProjectStatus ( `❌ Invalid LookML: ${ trunc ( firstError . error , 120 ) } ` ) ;
278+ setProject ( parsedProject ) ;
279+ setCtaDisabled ( true ) ;
280+ const errorMap = { } ;
281+ parsedProject . errors . forEach ( err => {
282+ errorMap [ err [ '$file_path' ] ] = err . error ;
283+ } ) ;
284+ setParseErrors ( errorMap ) ;
285+ return false ;
286+ } else {
287+ setParseErrors ( { } ) ;
288+ setProject ( parsedProject )
289+ setProjectStatus ( "✅ Project ready" )
290+ setCtaDisabled ( false )
291+ return true ;
292+ }
258293 } catch ( e ) {
294+ setParseErrors ( { } ) ;
259295 setProjectStatus ( `❌ Invalid LookML. ${ trunc ( e , 120 ) } ` )
260296 setProject ( undefined )
261297 setCtaDisabled ( true )
298+ return false ;
262299 }
263300 }
264301}
0 commit comments