77 */
88package com .intellij .lang .jsgraphql .ide .editor ;
99
10+ import com .google .common .collect .Sets ;
1011import com .google .gson .Gson ;
1112import com .intellij .ide .actions .CreateFileAction ;
1213import com .intellij .ide .impl .DataManagerImpl ;
1314import com .intellij .lang .jsgraphql .GraphQLSettings ;
1415import com .intellij .lang .jsgraphql .ide .project .graphqlconfig .GraphQLConfigManager ;
1516import com .intellij .lang .jsgraphql .ide .project .graphqlconfig .model .GraphQLConfigEndpoint ;
1617import com .intellij .lang .jsgraphql .ide .project .graphqlconfig .model .GraphQLConfigVariableAwareEndpoint ;
18+ import com .intellij .lang .jsgraphql .schema .GraphQLSchemaKeys ;
1719import com .intellij .lang .jsgraphql .v1 .ide .project .JSGraphQLLanguageUIProjectService ;
1820import com .intellij .notification .Notification ;
1921import com .intellij .notification .NotificationAction ;
2426import com .intellij .openapi .actionSystem .AnAction ;
2527import com .intellij .openapi .actionSystem .AnActionEvent ;
2628import com .intellij .openapi .application .ApplicationManager ;
29+ import com .intellij .openapi .components .ServiceManager ;
2730import com .intellij .openapi .editor .Editor ;
2831import com .intellij .openapi .fileEditor .FileEditor ;
2932import com .intellij .openapi .fileEditor .FileEditorManager ;
3033import com .intellij .openapi .fileEditor .TextEditor ;
34+ import com .intellij .openapi .progress .ProgressIndicator ;
3135import com .intellij .openapi .progress .ProgressManager ;
36+ import com .intellij .openapi .progress .Task ;
3237import com .intellij .openapi .project .Project ;
3338import com .intellij .openapi .vfs .VirtualFile ;
3439import com .intellij .psi .PsiDirectory ;
3540import com .intellij .psi .impl .file .PsiDirectoryFactory ;
3641import graphql .introspection .IntrospectionQuery ;
42+ import graphql .language .Description ;
3743import graphql .language .Document ;
44+ import graphql .language .Node ;
45+ import graphql .language .ScalarTypeDefinition ;
3846import graphql .schema .idl .SchemaPrinter ;
3947import org .apache .commons .httpclient .HttpClient ;
4048import org .apache .commons .httpclient .methods .PostMethod ;
4957import java .util .Date ;
5058import java .util .Map ;
5159import java .util .Optional ;
60+ import java .util .Set ;
5261
5362import static com .intellij .lang .jsgraphql .v1 .ide .project .JSGraphQLLanguageUIProjectService .setHeadersFromOptions ;
5463
5564public class GraphQLIntrospectionHelper {
5665
57- public static void performIntrospectionQueryAndUpdateSchemaPathFile (Project project , GraphQLConfigEndpoint endpoint ) {
66+ private GraphQLIntrospectionTask latestIntrospection = null ;
67+
68+ public static GraphQLIntrospectionHelper getService (@ NotNull Project project ) {
69+ return ServiceManager .getService (project , GraphQLIntrospectionHelper .class );
70+ }
71+
72+ public GraphQLIntrospectionHelper (Project project ) {
73+ if (project != null ) {
74+ project .getMessageBus ().connect ().subscribe (GraphQLConfigManager .TOPIC , () -> latestIntrospection = null );
75+ }
76+ }
77+
78+ public void performIntrospectionQueryAndUpdateSchemaPathFile (Project project , GraphQLConfigEndpoint endpoint ) {
5879 final VirtualFile configFile = GraphQLConfigManager .getService (project ).getClosestConfigFile (endpoint .configPackageSet .getConfigBaseDir ());
5980 if (configFile != null ) {
6081 final String schemaPath = endpoint .configPackageSet .getConfigData ().schemaPath ;
@@ -65,12 +86,14 @@ public static void performIntrospectionQueryAndUpdateSchemaPathFile(Project proj
6586 return ;
6687 }
6788
68- GraphQLIntrospectionHelper . performIntrospectionQueryAndUpdateSchemaPathFile (project , new GraphQLConfigVariableAwareEndpoint (endpoint , project ), schemaPath , configFile );
89+ performIntrospectionQueryAndUpdateSchemaPathFile (project , new GraphQLConfigVariableAwareEndpoint (endpoint , project ), schemaPath , configFile );
6990 }
7091
7192 }
7293
73- public static void performIntrospectionQueryAndUpdateSchemaPathFile (Project project , GraphQLConfigVariableAwareEndpoint endpoint , String schemaPath , VirtualFile introspectionSourceFile ) {
94+ public void performIntrospectionQueryAndUpdateSchemaPathFile (Project project , GraphQLConfigVariableAwareEndpoint endpoint , String schemaPath , VirtualFile introspectionSourceFile ) {
95+
96+ latestIntrospection = new GraphQLIntrospectionTask (endpoint , () -> performIntrospectionQueryAndUpdateSchemaPathFile (project , endpoint , schemaPath , introspectionSourceFile ));
7497
7598 final NotificationAction retry = new NotificationAction ("Retry" ) {
7699
@@ -99,34 +122,37 @@ public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification noti
99122
100123 setHeadersFromOptions (endpoint , method );
101124
102- ProgressManager .getInstance ().runProcessWithProgressSynchronously (() -> {
103- ProgressManager .getInstance ().getProgressIndicator ().setIndeterminate (true );
104- try {
105- httpClient .executeMethod (method );
106- final String responseJson = Optional .ofNullable (method .getResponseBodyAsString ()).orElse ("" );
107- ApplicationManager .getApplication ().invokeLater (() -> {
108- try {
109- JSGraphQLLanguageUIProjectService .getService (project ).showQueryResult (responseJson );
110- final String schemaAsSDL = printIntrospectionJsonAsGraphQL (responseJson );
111- createOrUpdateIntrospectionSDLFile (schemaAsSDL , introspectionSourceFile , schemaPath , project );
112- } catch (Exception e ) {
113- Notifications .Bus .notify (new Notification ("GraphQL" , "GraphQL Introspection Error" , e .getMessage (), NotificationType .WARNING ).addAction (retry ), project );
114- }
115- });
116- } catch (IOException e ) {
117- Notifications .Bus .notify (new Notification ("GraphQL" , "GraphQL Query Error" , url + ": " + e .getMessage (), NotificationType .WARNING ).addAction (retry ), project );
125+ final Task .Backgroundable task = new Task .Backgroundable (project , "Executing GraphQL Introspection Query" , false ) {
126+ @ Override
127+ public void run (@ NotNull ProgressIndicator indicator ) {
128+ indicator .setIndeterminate (true );
129+ try {
130+ httpClient .executeMethod (method );
131+ final String responseJson = Optional .ofNullable (method .getResponseBodyAsString ()).orElse ("" );
132+ ApplicationManager .getApplication ().invokeLater (() -> {
133+ try {
134+ JSGraphQLLanguageUIProjectService .getService (project ).showQueryResult (responseJson , JSGraphQLLanguageUIProjectService .QueryResultDisplay .ON_ERRORS_ONLY );
135+ IntrospectionOutputFormat format = schemaPath .endsWith (".json" ) ? IntrospectionOutputFormat .JSON : IntrospectionOutputFormat .SDL ;
136+ final String schemaText = format == IntrospectionOutputFormat .SDL ? printIntrospectionJsonAsGraphQL (responseJson ) : responseJson ;
137+ createOrUpdateIntrospectionOutputFile (schemaText , format , introspectionSourceFile , schemaPath , project );
138+ } catch (Exception e ) {
139+ Notifications .Bus .notify (new Notification ("GraphQL" , "GraphQL Introspection Error" , e .getMessage (), NotificationType .WARNING ).addAction (retry ), project );
140+ }
141+ });
142+ } catch (IOException e ) {
143+ Notifications .Bus .notify (new Notification ("GraphQL" , "GraphQL Query Error" , url + ": " + e .getMessage (), NotificationType .WARNING ).addAction (retry ), project );
144+ }
118145 }
119-
120- }, "Executing GraphQL Introspection Query" , false , project );
121-
146+ };
147+ ProgressManager .getInstance ().run (task );
122148
123149 } catch (UnsupportedEncodingException | IllegalStateException | IllegalArgumentException e ) {
124150 Notifications .Bus .notify (new Notification ("GraphQL" , "GraphQL Query Error" , url + ": " + e .getMessage (), NotificationType .ERROR ).addAction (retry ), project );
125151 }
126152 }
127153
128154 @ SuppressWarnings ("unchecked" )
129- public static String printIntrospectionJsonAsGraphQL (String introspectionJson ) {
155+ public String printIntrospectionJsonAsGraphQL (String introspectionJson ) {
130156 Map <String , Object > introspectionAsMap = new Gson ().fromJson (introspectionJson , Map .class );
131157 if (!introspectionAsMap .containsKey ("__schema" )) {
132158 // possibly a full query result
@@ -142,30 +168,73 @@ public static String printIntrospectionJsonAsGraphQL(String introspectionJson) {
142168 }
143169 }
144170 final Document schemaDefinition = new GraphQLIntrospectionResultToSchema ().createSchemaDefinition (introspectionAsMap );
145- return new SchemaPrinter ().print (schemaDefinition );
171+ final SchemaPrinter .Options options = SchemaPrinter .Options .defaultOptions ().includeScalarTypes (true ).includeSchemaDefintion (true );
172+ final StringBuilder sb = new StringBuilder (new SchemaPrinter (options ).print (schemaDefinition ));
173+
174+ // graphql-java currently appears to discard custom scalars as part of the schema that the printer constructs, so include them manually here
175+ final Set <String > knownScalars = Sets .newHashSet ();
176+ for (Node definition : schemaDefinition .getChildren ()) {
177+ if (definition instanceof ScalarTypeDefinition ) {
178+ final ScalarTypeDefinition scalarTypeDefinition = (ScalarTypeDefinition ) definition ;
179+ String scalarName = scalarTypeDefinition .getName ();
180+ if (knownScalars .add (scalarName )) {
181+ sb .append ("\n " );
182+ final Description description = scalarTypeDefinition .getDescription ();
183+ if (description != null ) {
184+ if (description .isMultiLine ()) {
185+ sb .append ("\" \" \" " ).append (description .getContent ()).append ("\" \" \" " );
186+ } else {
187+ sb .append ("\" " ).append (description .getContent ()).append ("\" " );
188+ }
189+ sb .append ("\n " );
190+ }
191+ sb .append ("scalar " ).append (scalarName );
192+ }
193+ }
194+ }
195+ return sb .toString ();
146196 }
147197
198+ public GraphQLIntrospectionTask getLatestIntrospection () {
199+ return latestIntrospection ;
200+ }
201+
202+ enum IntrospectionOutputFormat {
203+ JSON ,
204+ SDL
205+ }
148206
149- static void createOrUpdateIntrospectionSDLFile (String schemaAsSDL , VirtualFile introspectionSourceFile , String outputFileName , Project project ) {
207+ void createOrUpdateIntrospectionOutputFile (String schemaText , IntrospectionOutputFormat format , VirtualFile introspectionSourceFile , String outputFileName , Project project ) {
150208 ApplicationManager .getApplication ().runWriteAction (() -> {
151209 try {
152- final String schemaAsSDLWithHeader = "# This file was generated based on \" " + introspectionSourceFile .getName () + "\" at " + new Date () + ". Do not edit manually.\n \n " + schemaAsSDL ;
210+ final String header ;
211+ switch (format ) {
212+ case SDL :
213+ header = "# This file was generated based on \" " + introspectionSourceFile .getName () + "\" at " + new Date () + ". Do not edit manually.\n \n " ;
214+ break ;
215+ case JSON :
216+ header = "" ;
217+ break ;
218+ default :
219+ throw new IllegalArgumentException ("unsupported output format: " + format );
220+ }
153221 String relativeOutputFileName = StringUtils .replaceChars (outputFileName , '\\' , '/' );
154222 VirtualFile outputFile = introspectionSourceFile .getParent ().findFileByRelativePath (relativeOutputFileName );
155223 if (outputFile == null ) {
156224 PsiDirectory directory = PsiDirectoryFactory .getInstance (project ).createDirectory (introspectionSourceFile .getParent ());
157225 CreateFileAction .MkDirs dirs = new CreateFileAction .MkDirs (relativeOutputFileName , directory );
158226 outputFile = dirs .directory .getVirtualFile ().createChildData (introspectionSourceFile , dirs .newName );
159227 }
228+ outputFile .putUserData (GraphQLSchemaKeys .IS_GRAPHQL_INTROSPECTION_JSON , true );
160229 final FileEditor fileEditor = FileEditorManager .getInstance (project ).openFile (outputFile , true , true )[0 ];
161- setEditorTextAndFormatLines (schemaAsSDLWithHeader , fileEditor );
230+ setEditorTextAndFormatLines (header + schemaText , fileEditor );
162231 } catch (IOException ioe ) {
163232 Notifications .Bus .notify (new Notification ("GraphQL" , "GraphQL IO Error" , "Unable to create file '" + outputFileName + "' in directory '" + introspectionSourceFile .getParent ().getPath () + "': " + ioe .getMessage (), NotificationType .ERROR ));
164233 }
165234 });
166235 }
167236
168- static void setEditorTextAndFormatLines (String text , FileEditor fileEditor ) {
237+ void setEditorTextAndFormatLines (String text , FileEditor fileEditor ) {
169238 if (fileEditor instanceof TextEditor ) {
170239 final Editor editor = ((TextEditor ) fileEditor ).getEditor ();
171240 editor .getDocument ().setText (text );
0 commit comments