1- import { executeRequestInAsync } from "@modules/app/requestExecutor" ;
1+ import {
2+ executeRequestInAsync ,
3+ executeRequestInSync ,
4+ } from "@modules/app/requestExecutor" ;
25import { IncomingMessageProps } from "@modules/app/types" ;
36import { panelLogger } from "@modules/logger" ;
47import {
@@ -7,6 +10,7 @@ import {
710 useEffect ,
811 useMemo ,
912 useReducer ,
13+ useRef ,
1014} from "react" ;
1115import documentationSlice , {
1216 initialState ,
@@ -21,6 +25,7 @@ import documentationSlice, {
2125 updateColumnsInCurrentDocsData ,
2226 updateConversationsRightPanelState ,
2327 updateCurrentDocsData ,
28+ updateCurrentDocsTests ,
2429 updateSelectedConversationGroup ,
2530 updateUserInstructions ,
2631} from "./state/documentationSlice" ;
@@ -31,7 +36,7 @@ import {
3136 MetadataColumn ,
3237} from "./state/types" ;
3338import { ContextProps } from "./types" ;
34- import { getGenerationsInModel } from "./utils" ;
39+ import { getGenerationsInModel , isStateDirty } from "./utils" ;
3540import DocumentationEditor from "./DocumentationEditor" ;
3641import { ConversationGroup , DbtDocsShareDetails } from "@lib" ;
3742import { TelemetryEvents } from "@telemetryEvents" ;
@@ -43,14 +48,38 @@ export const DocumentationContext = createContext<ContextProps>({
4348 dispatch : ( ) => null ,
4449} ) ;
4550
51+ type IncomingMessageEvent = MessageEvent <
52+ IncomingMessageProps & {
53+ docs ?: DBTDocumentation ;
54+ tests ?: DBTModelTest [ ] ;
55+ project ?: string ;
56+ columns ?: DBTDocumentation [ "columns" ] ;
57+ model ?: string ;
58+ name ?: string ;
59+ description ?: string ;
60+ collaborationEnabled ?: boolean ;
61+ missingDocumentationMessage ?: {
62+ message : string ;
63+ type : "error" | "warning" ;
64+ } ;
65+ }
66+ > ;
67+
68+ enum ActionState {
69+ CANCEL_STAY = "Stay" ,
70+ DISCARD_PROCEED = "Discard" ,
71+ SAVE_PROCEED = "Save changes" ,
72+ }
73+
4674const DocumentationProvider = ( ) : JSX . Element => {
4775 const {
4876 state : { isComponentsApiInitialized } ,
4977 } = useAppContext ( ) ;
5078 const [ state , dispatch ] = useReducer (
5179 documentationSlice . reducer ,
52- documentationSlice . getInitialState ( )
80+ documentationSlice . getInitialState ( ) ,
5381 ) ;
82+ const stateRef = useRef ( state ) ;
5483
5584 const updateFocus = ( name ?: string ) => {
5685 dispatch ( setInsertedEntityName ( name ) ) ;
@@ -84,107 +113,147 @@ const DocumentationProvider = (): JSX.Element => {
84113 updateSelectedConversationGroup ( {
85114 shareId,
86115 conversationGroupId : conversation_group_id ,
87- } )
116+ } ) ,
88117 ) ;
89118 } ;
90119
91- const onMessage = useCallback (
92- (
93- event : MessageEvent <
94- IncomingMessageProps & {
95- docs ?: DBTDocumentation ;
96- tests ?: DBTModelTest [ ] ;
97- project ?: string ;
98- columns ?: DBTDocumentation [ "columns" ] ;
99- model ?: string ;
100- name ?: string ;
101- description ?: string ;
102- collaborationEnabled ?: boolean ;
103- missingDocumentationMessage ?: {
104- message : string ;
105- type : "error" | "warning" ;
106- } ;
107- }
108- >
109- ) => {
110- const { command, ...params } = event . data ;
111- switch ( command ) {
112- case "viewConversation" :
113- handleViewConversation (
114- params as unknown as Parameters < typeof handleViewConversation > [ "0" ]
115- ) ;
120+ const renderDocumentation = ( event : IncomingMessageEvent ) => {
121+ dispatch (
122+ setIncomingDocsData ( {
123+ docs : event . data . docs ,
124+ tests : event . data . tests ,
125+ } ) ,
126+ ) ;
127+ dispatch ( setProject ( event . data . project ) ) ;
128+ dispatch (
129+ updateCollaborationEnabled ( Boolean ( event . data . collaborationEnabled ) ) ,
130+ ) ;
131+ dispatch (
132+ setMissingDocumentationMessage ( event . data . missingDocumentationMessage ) ,
133+ ) ;
134+ } ;
135+
136+ const onMessage = useCallback ( ( event : IncomingMessageEvent ) => {
137+ const { command, ...params } = event . data ;
138+ switch ( command ) {
139+ case "viewConversation" :
140+ handleViewConversation (
141+ params as unknown as Parameters < typeof handleViewConversation > [ "0" ] ,
142+ ) ;
143+ break ;
144+ case "conversations:updates" :
145+ handleConversationUpdates (
146+ params as unknown as Parameters <
147+ typeof handleConversationUpdates
148+ > [ "0" ] ,
149+ ) ;
150+ break ;
151+ case "renderDocumentation" : {
152+ if (
153+ event . data . docs ?. uniqueId ===
154+ stateRef . current . currentDocsData ?. uniqueId
155+ ) {
116156 break ;
117- case "conversations:updates" :
118- handleConversationUpdates (
119- params as unknown as Parameters <
120- typeof handleConversationUpdates
121- > [ "0" ]
122- ) ;
157+ }
158+ if ( ! isStateDirty ( stateRef . current ) ) {
159+ renderDocumentation ( event ) ;
123160 break ;
124- case "renderDocumentation" :
125- dispatch (
126- setIncomingDocsData ( {
127- docs : event . data . docs ,
128- tests : event . data . tests ,
129- } )
130- ) ;
131- dispatch ( setProject ( event . data . project ) ) ;
132- dispatch (
133- updateCollaborationEnabled ( Boolean ( event . data . collaborationEnabled ) )
134- ) ;
161+ }
162+ const { currentDocsData, currentDocsTests } = stateRef . current ;
163+ executeRequestInSync ( "showWarningMessage" , {
164+ infoMessage : `You have unsaved changes in model: ‘${ currentDocsData ?. name } ’. Would you
165+ like to discard the changes, save them and proceed, or remain in the
166+ current state?` ,
167+ items : [
168+ ActionState . DISCARD_PROCEED ,
169+ ActionState . CANCEL_STAY ,
170+ ActionState . SAVE_PROCEED ,
171+ ] ,
172+ } )
173+ . then ( async ( action ) => {
174+ switch ( action ) {
175+ case ActionState . SAVE_PROCEED : {
176+ const result = ( await executeRequestInSync (
177+ "saveDocumentation" ,
178+ {
179+ ...currentDocsData ,
180+ updatedTests : currentDocsTests ,
181+ dialogType : "Existing file" ,
182+ } ,
183+ ) ) as { saved : boolean } ;
184+ if ( result . saved ) {
185+ dispatch ( updateCurrentDocsData ( event . data . docs ) ) ;
186+ dispatch ( updateCurrentDocsTests ( event . data . tests ) ) ;
187+ }
188+ renderDocumentation ( event ) ;
189+ break ;
190+ }
191+ case ActionState . DISCARD_PROCEED : {
192+ dispatch ( updateCurrentDocsData ( event . data . docs ) ) ;
193+ dispatch ( updateCurrentDocsTests ( event . data . tests ) ) ;
194+ renderDocumentation ( event ) ;
195+ break ;
196+ }
197+ case ActionState . CANCEL_STAY : {
198+ break ;
199+ }
200+ default :
201+ break ;
202+ }
203+ } )
204+ . catch ( ( err ) => {
205+ panelLogger . error (
206+ "error while showing unsaved changes dialog" ,
207+ err ,
208+ ) ;
209+ } ) ;
210+ break ;
211+ }
212+ case "renderColumnsFromMetadataFetch" :
213+ if ( event . data . columns ) {
135214 dispatch (
136- setMissingDocumentationMessage (
137- event . data . missingDocumentationMessage
138- )
215+ updateColumnsAfterSync ( {
216+ columns : event . data . columns ,
217+ } ) ,
139218 ) ;
140- break ;
141- case "renderColumnsFromMetadataFetch" :
142- if ( event . data . columns ) {
143- dispatch (
144- updateColumnsAfterSync ( {
145- columns : event . data . columns ,
146- } )
147- ) ;
148- }
149- break ;
150- case "docgen:insert" :
151- panelLogger . info ( "received new doc gen" , event . data ) ;
152- // insert model desc
153- if ( params . model ) {
154- dispatch (
155- updateCurrentDocsData ( {
156- description : params . description ,
157- name : params . model ,
158- isNewGeneration : true ,
159- } )
160- ) ;
161- updateFocus ( params . model ) ;
162- return ;
163- }
164- // insert column desc
219+ }
220+ break ;
221+ case "docgen:insert" :
222+ panelLogger . info ( "received new doc gen" , event . data ) ;
223+ // insert model desc
224+ if ( params . model ) {
165225 dispatch (
166- updateColumnsInCurrentDocsData ( {
167- columns : [ params as Partial < MetadataColumn > ] ,
226+ updateCurrentDocsData ( {
227+ description : params . description ,
228+ name : params . model ,
168229 isNewGeneration : true ,
169- } )
230+ } ) ,
170231 ) ;
171- updateFocus ( ( params as Partial < MetadataColumn > ) . name ) ;
232+ updateFocus ( params . model ) ;
233+ return ;
234+ }
235+ // insert column desc
236+ dispatch (
237+ updateColumnsInCurrentDocsData ( {
238+ columns : [ params as Partial < MetadataColumn > ] ,
239+ isNewGeneration : true ,
240+ } ) ,
241+ ) ;
242+ updateFocus ( ( params as Partial < MetadataColumn > ) . name ) ;
172243
173- break ;
174- default :
175- break ;
176- }
177- } ,
178- [ ]
179- ) ;
244+ break ;
245+ default :
246+ break ;
247+ }
248+ } , [ ] ) ;
180249
181250 const loadGenerationsHistory = ( project : string , model : string ) => {
182251 getGenerationsInModel ( project , model )
183252 . then ( ( data ) => {
184253 dispatch ( setGenerationsHistory ( data ) ) ;
185254 } )
186255 . catch ( ( err ) =>
187- panelLogger . error ( "error while loading generations history" , err )
256+ panelLogger . error ( "error while loading generations history" , err ) ,
188257 ) ;
189258 } ;
190259
@@ -197,8 +266,8 @@ const DocumentationProvider = (): JSX.Element => {
197266 if ( userInstructions ) {
198267 dispatch (
199268 updateUserInstructions (
200- JSON . parse ( userInstructions ) as DocsGenerateUserInstructions
201- )
269+ JSON . parse ( userInstructions ) as DocsGenerateUserInstructions ,
270+ ) ,
202271 ) ;
203272 }
204273 loadGenerationsHistory ( state . project , state . currentDocsData . name ) ;
@@ -220,9 +289,14 @@ const DocumentationProvider = (): JSX.Element => {
220289 state,
221290 dispatch,
222291 } ) ,
223- [ state , dispatch ]
292+ [ state , dispatch ] ,
224293 ) ;
225294
295+ // hack to get latest state in onMessage
296+ useEffect ( ( ) => {
297+ stateRef . current = state ;
298+ } , [ state ] ) ;
299+
226300 if ( ! isComponentsApiInitialized ) {
227301 return < div > Loading...</ div > ;
228302 }
0 commit comments