View Javadoc
1   /*
2    * Copyright (c) 2002-2021, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.kibana.modules.forms.service;
35  
36  import java.util.List;
37  import fr.paris.lutece.plugins.elasticdata.business.DataSource;
38  import fr.paris.lutece.plugins.elasticdata.modules.forms.business.OptionalQuestionIndexation;
39  import fr.paris.lutece.plugins.elasticdata.modules.forms.business.OptionalQuestionIndexationHome;
40  import fr.paris.lutece.plugins.forms.business.Form;
41  import fr.paris.lutece.plugins.forms.business.FormHome;
42  import fr.paris.lutece.plugins.forms.business.Question;
43  import fr.paris.lutece.plugins.forms.business.QuestionHome;
44  import fr.paris.lutece.plugins.kibana.business.DashboardReference;
45  import fr.paris.lutece.plugins.kibana.business.GirdLayout;
46  import fr.paris.lutece.plugins.kibana.service.IDataVisualizerService;
47  import fr.paris.lutece.plugins.kibana.service.SavedObjectService;
48  import fr.paris.lutece.plugins.kibana.service.VisualizationService;
49  import fr.paris.lutece.portal.business.event.ResourceEvent;
50  import net.sf.json.JSONArray;
51  import net.sf.json.JSONObject;
52  
53  /**
54   *
55   * ResourceHistoryService
56   *
57   */
58  public class DataVisualizerService implements IDataVisualizerService
59  {
60      public static final String DATA_SOURCE_ID = "FormsDataSource";
61  
62      @Override
63      public void createOrUpdate( ResourceEvent event, DataSource dataSource )
64      {
65          List<Form> listForms = FormHome.getFormList( );
66          String strIdIndexPattern = dataSource.getTargetIndexName( );
67          for ( Form form : listForms )
68          {
69              JSONArray jsonArrayReference = new JSONArray( );
70              JSONArray jsonArraypanelsJSON = new JSONArray( );
71              String strIdDashboard = event.getIdResource( ) + "_" + form.getId( );
72              GirdLayout girdLayout = new GirdLayout( );
73              String strQueryFilterFormResponse = "documentTypeName.keyword : formResponse and formId : " + form.getId( );
74              initDashboard( jsonArrayReference, jsonArraypanelsJSON, strIdDashboard, strIdIndexPattern, girdLayout, form.getId( ) );
75              SavedObjectService.deleteDashboard( strIdDashboard );
76              List<OptionalQuestionIndexation> optionalQuestionIndexations = OptionalQuestionIndexationHome
77                      .getOptionalQuestionIndexationListByFormId( form.getId( ) );
78              if ( optionalQuestionIndexations != null )
79              {
80                  for ( OptionalQuestionIndexation optionalQuestionIndexation : optionalQuestionIndexations )
81                  {
82                      int idQuestion = optionalQuestionIndexation.getIdQuestion( );
83                      Question question = QuestionHome.findByPrimaryKey( idQuestion );
84                      String strType = question.getEntry( ).getEntryType( ).getBeanName( );
85                      String fieldName = getFieldName( strType, question.getId( ), question.getTitle( ) );
86                      if ( strType.contains( "entryTypeSelect" ) || strType.contains( "entryTypeCheckBox" ) || strType.contains( "entryTypeRadioButton" ) )
87                      {
88                          DashboardReference dashboardReferenceLens = new DashboardReference( "lens" );
89                          jsonArrayReference.add( dashboardReferenceLens );
90                          jsonArraypanelsJSON.add( SavedObjectService.createPanelJson( dashboardReferenceLens.getName( ), girdLayout.getPos( 12, 10 ) ) );
91                          VisualizationService.createDonutLensVisualization( question.getTitle( ), fieldName, dashboardReferenceLens.getId( ), strIdIndexPattern,
92                                  strQueryFilterFormResponse );
93                          DashboardReference dashboardReferenceTable = new DashboardReference( );
94                          jsonArrayReference.add( dashboardReferenceTable );
95                          jsonArraypanelsJSON.add( SavedObjectService.createPanelJson( dashboardReferenceTable.getName( ), girdLayout.getPos( 12, 10 ) ) );
96                          VisualizationService.createDataTableTopValueVisualization( question.getTitle( ), fieldName, dashboardReferenceTable.getId( ),
97                                  strIdIndexPattern, strQueryFilterFormResponse );
98                      }
99                      if ( strType.contains( "entryTypeGeolocation" ) )
100                     {
101                         DashboardReference dashboardReferenceMap = new DashboardReference( "map" );
102                         jsonArrayReference.add( dashboardReferenceMap );
103                         jsonArraypanelsJSON.add( SavedObjectService.createPanelJson( dashboardReferenceMap.getName( ), girdLayout.getPos( 24, 10 ) ) );
104                         VisualizationService.createMapVisualization( question.getTitle( ), fieldName, dashboardReferenceMap.getId( ), strIdIndexPattern,
105                                 strQueryFilterFormResponse );
106                         DashboardReference dashboardReferenceTable = new DashboardReference( );
107                         jsonArrayReference.add( dashboardReferenceTable );
108                         jsonArraypanelsJSON.add( SavedObjectService.createPanelJson( dashboardReferenceTable.getName( ), girdLayout.getPos( 24, 10 ) ) );
109                         VisualizationService.createDataTableTopValueVisualization( question.getTitle( ),
110                                 fieldName.replace( "elastic.geopoint", "address.keyword" ), dashboardReferenceTable.getId( ), strIdIndexPattern,
111                                 strQueryFilterFormResponse );
112                     }
113                     if ( strType.contains( "entryTypeText" ) || strType.contains( "entryTypeTextArea" ) )
114                     {
115                         DashboardReference dashboardReferenceTable = new DashboardReference( );
116                         jsonArrayReference.add( dashboardReferenceTable );
117                         jsonArraypanelsJSON.add( SavedObjectService.createPanelJson( dashboardReferenceTable.getName( ), girdLayout.getPos( 24, 10 ) ) );
118                         VisualizationService.createDataTableTopValueVisualization( question.getTitle( ), fieldName, dashboardReferenceTable.getId( ),
119                                 strIdIndexPattern, strQueryFilterFormResponse );
120                     }
121                 }
122             }
123             SavedObjectService.createDashboard( strIdDashboard, form.getTitle( ), strIdIndexPattern, jsonArrayReference, jsonArraypanelsJSON, dataSource );
124         }
125     }
126 
127     @Override
128     public Boolean isExistDataSourceDataVisualizer( DataSource datasource )
129     {
130         if ( datasource.getId( ).equals( DATA_SOURCE_ID ) )
131         {
132             return true;
133         }
134         return false;
135     }
136 
137     private static void initDashboard( JSONArray jsonArrayReference, JSONArray jsonArraypanelsJSON, String strIdDashboard, String strIdIndexPattern,
138             GirdLayout girdLayout, int formId )
139     {
140         DashboardReference dashboardReference = new DashboardReference( );
141         jsonArrayReference.add( dashboardReference );
142         jsonArraypanelsJSON.add( SavedObjectService.createPanelJson( dashboardReference.getName( ), girdLayout.getPos( 36, 10 ) ) );
143         createHistoResponseVisualization( "Histogramme des demandes", dashboardReference.getId( ), strIdIndexPattern, formId );
144         dashboardReference = new DashboardReference( );
145         jsonArrayReference.add( dashboardReference );
146         jsonArraypanelsJSON.add( SavedObjectService.createPanelJson( dashboardReference.getName( ), girdLayout.getPos( 12, 10 ) ) );
147         createResponseByMonthVisualization( "Répartition par mois", dashboardReference.getId( ), strIdIndexPattern, formId );
148         dashboardReference = new DashboardReference( );
149         jsonArrayReference.add( dashboardReference );
150         jsonArraypanelsJSON.add( SavedObjectService.createPanelJson( dashboardReference.getName( ), girdLayout.getPos( 24, 10 ) ) );
151         createWorkflowDurationVisualization( "Répartition par état des demandes", dashboardReference.getId( ), strIdIndexPattern, formId );
152         dashboardReference = new DashboardReference( );
153         jsonArrayReference.add( dashboardReference );
154         jsonArraypanelsJSON.add( SavedObjectService.createPanelJson( dashboardReference.getName( ), girdLayout.getPos( 24, 10 ) ) );
155         createWorkflowHistoryDurationVisualization( "Répartition de l'historique des workflows par état", dashboardReference.getId( ), strIdIndexPattern,
156                 formId );
157     }
158 
159     private static String getFieldName( String strEntryType, int strIdQuestion, String strQuestionTitle )
160     {
161         String strFieldName = "userResponses." + strIdQuestion + "." + strQuestionTitle;
162         if ( strEntryType.contains( "entryTypeSelect" ) || strEntryType.contains( "entryTypeRadioButton" ) )
163         {
164             strFieldName += ".answer_choice.keyword";
165         }
166         if ( strEntryType.contains( "entryTypeCheckBox" ) )
167         {
168             strFieldName = "userResponsesMultiValued." + strIdQuestion + "." + strQuestionTitle + ".keyword";
169         }
170         if ( strEntryType.contains( "entryTypeText" ) || strEntryType.contains( "entryTypeTextArea" ) )
171         {
172             strFieldName += ".keyword";
173         }
174         if ( strEntryType.contains( "entryTypeGeolocation" ) )
175         {
176             strFieldName += ".elastic.geopoint";
177         }
178         return strFieldName;
179     }
180 
181     /**
182      * create form response histogram visualization
183      * 
184      * @param visualization
185      *            visualisation
186      */
187     private static void createHistoResponseVisualization( String strTitle, String strIdVisualization, String strIdIndexPattern, int nFormId )
188     {
189         JSONObject bar = VisualizationService.getVisualizationObject( strTitle, strIdVisualization, strIdIndexPattern,
190                 "documentTypeName.keyword : formResponse and formId : " + nFormId );
191         String visState = "{\\\"title\\\":\\\"Histogramme des demandes\\\",\\\"type\\\":\\\"histogram\\\",\\\"aggs\\\":[{\\\"id\\\":\\\"1\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"count\\\",\\\"params\\\":{\\\"customLabel\\\":\\\"Nombre de demandes\\\"},\\\"schema\\\":\\\"metric\\\"},{\\\"id\\\":\\\"2\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"date_histogram\\\",\\\"params\\\":{\\\"field\\\":\\\"timestamp\\\",\\\"timeRange\\\":{\\\"from\\\":\\\"now-1y\\\",\\\"to\\\":\\\"now\\\"},\\\"useNormalizedEsInterval\\\":true,\\\"scaleMetricValues\\\":false,\\\"interval\\\":\\\"auto\\\",\\\"drop_partials\\\":false,\\\"min_doc_count\\\":1,\\\"extended_bounds\\\":{},\\\"customLabel\\\":\\\" \\\"},\\\"schema\\\":\\\"segment\\\"},{\\\"id\\\":\\\"3\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"terms\\\",\\\"params\\\":{\\\"field\\\":\\\"workflowState.keyword\\\",\\\"orderBy\\\":\\\"1\\\",\\\"order\\\":\\\"desc\\\",\\\"size\\\":150,\\\"otherBucket\\\":false,\\\"otherBucketLabel\\\":\\\"Other\\\",\\\"missingBucket\\\":true,\\\"missingBucketLabel\\\":\\\"Sans workflow\\\",\\\"customLabel\\\":\\\"Statut\\\"},\\\"schema\\\":\\\"group\\\"}],\\\"params\\\":{\\\"type\\\":\\\"histogram\\\",\\\"grid\\\":{\\\"categoryLines\\\":false},\\\"categoryAxes\\\":[{\\\"id\\\":\\\"CategoryAxis-1\\\",\\\"type\\\":\\\"category\\\",\\\"position\\\":\\\"bottom\\\",\\\"show\\\":true,\\\"style\\\":{},\\\"scale\\\":{\\\"type\\\":\\\"linear\\\"},\\\"labels\\\":{\\\"show\\\":true,\\\"filter\\\":true,\\\"truncate\\\":100},\\\"title\\\":{}}],\\\"valueAxes\\\":[{\\\"id\\\":\\\"ValueAxis-1\\\",\\\"name\\\":\\\"LeftAxis-1\\\",\\\"type\\\":\\\"value\\\",\\\"position\\\":\\\"left\\\",\\\"show\\\":true,\\\"style\\\":{},\\\"scale\\\":{\\\"type\\\":\\\"linear\\\",\\\"mode\\\":\\\"normal\\\"},\\\"labels\\\":{\\\"show\\\":true,\\\"rotate\\\":0,\\\"filter\\\":false,\\\"truncate\\\":100},\\\"title\\\":{\\\"text\\\":\\\"Nombre de demandes\\\"}}],\\\"seriesParams\\\":[{\\\"show\\\":true,\\\"type\\\":\\\"histogram\\\",\\\"mode\\\":\\\"stacked\\\",\\\"data\\\":{\\\"label\\\":\\\"Nombre de demandes\\\",\\\"id\\\":\\\"1\\\"},\\\"valueAxis\\\":\\\"ValueAxis-1\\\",\\\"drawLinesBetweenPoints\\\":true,\\\"lineWidth\\\":2,\\\"showCircles\\\":true}],\\\"addTooltip\\\":true,\\\"addLegend\\\":true,\\\"legendPosition\\\":\\\"right\\\",\\\"times\\\":[],\\\"addTimeMarker\\\":true,\\\"labels\\\":{\\\"show\\\":true},\\\"thresholdLine\\\":{\\\"show\\\":false,\\\"value\\\":10,\\\"width\\\":1,\\\"style\\\":\\\"full\\\",\\\"color\\\":\\\"#E7664C\\\"}}}";
192         ( (JSONObject) bar.get( "attributes" ) ).put( "visState", visState );
193         SavedObjectService.create( bar );
194     }
195 
196     /**
197      * create form response histogram visualization
198      * 
199      * @param visualization
200      *            visualisation
201      */
202     private static void createWorkflowDurationVisualization( String strTitle, String strIdVisualization, String strIdIndexPattern, int nFormId )
203     {
204         JSONObject bar = VisualizationService.getVisualizationObject( strTitle, strIdVisualization, strIdIndexPattern,
205                 "documentTypeName.keyword : formResponse and formId : " + nFormId );
206         String visState = "{\\\"title\\\":\\\"" + strTitle
207                 + "\\\",\\\"type\\\":\\\"table\\\",\\\"aggs\\\":[{\\\"id\\\":\\\"1\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"count\\\",\\\"params\\\":{\\\"customLabel\\\":\\\"Nombre de demandes\\\"},\\\"schema\\\":\\\"metric\\\"},{\\\"id\\\":\\\"2\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"terms\\\",\\\"params\\\":{\\\"field\\\":\\\"workflowState.keyword\\\",\\\"orderBy\\\":\\\"1\\\",\\\"order\\\":\\\"desc\\\",\\\"size\\\":150,\\\"otherBucket\\\":false,\\\"otherBucketLabel\\\":\\\"Other\\\",\\\"missingBucket\\\":false,\\\"missingBucketLabel\\\":\\\"Missing\\\",\\\"customLabel\\\":\\\"Etat du workflow\\\"},\\\"schema\\\":\\\"bucket\\\"},{\\\"id\\\":\\\"3\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"avg\\\",\\\"params\\\":{\\\"field\\\":\\\"completeDuration\\\",\\\"customLabel\\\":\\\"Durée moyenne avant l'état\\\"},\\\"schema\\\":\\\"metric\\\"}],\\\"params\\\":{\\\"perPage\\\":150,\\\"showPartialRows\\\":false,\\\"showMetricsAtAllLevels\\\":false,\\\"sort\\\":{\\\"columnIndex\\\":null,\\\"direction\\\":null},\\\"showTotal\\\":false,\\\"totalFunc\\\":\\\"sum\\\",\\\"percentageCol\\\":\\\"Nombre de demandes\\\"}}";
208         ( (JSONObject) bar.get( "attributes" ) ).put( "visState", visState );
209         SavedObjectService.create( bar );
210     }
211 
212     /**
213      * create form response history histogram visualization
214      * 
215      * @param visualization
216      *            visualisation
217      */
218     private static void createWorkflowHistoryDurationVisualization( String strTitle, String strIdVisualization, String strIdIndexPattern, int nFormId )
219     {
220         JSONObject bar = VisualizationService.getVisualizationObject( strTitle, strIdVisualization, strIdIndexPattern,
221                 "documentTypeName.keyword : formResponseHistory and formId : " + nFormId );
222         String visState = "{\\\"title\\\":\\\"" + strTitle
223                 + "\\\",\\\"type\\\":\\\"table\\\",\\\"aggs\\\":[{\\\"id\\\":\\\"1\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"count\\\",\\\"params\\\":{\\\"customLabel\\\":\\\"Nombre d'historique\\\"},\\\"schema\\\":\\\"metric\\\"},{\\\"id\\\":\\\"2\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"terms\\\",\\\"params\\\":{\\\"field\\\":\\\"workflowState.keyword\\\",\\\"orderBy\\\":\\\"1\\\",\\\"order\\\":\\\"desc\\\",\\\"size\\\":150,\\\"otherBucket\\\":false,\\\"otherBucketLabel\\\":\\\"Other\\\",\\\"missingBucket\\\":false,\\\"missingBucketLabel\\\":\\\"Missing\\\",\\\"customLabel\\\":\\\"Etat du workflow\\\"},\\\"schema\\\":\\\"bucket\\\"},{\\\"id\\\":\\\"3\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"avg\\\",\\\"params\\\":{\\\"field\\\":\\\"completeDuration\\\",\\\"customLabel\\\":\\\"Durée moyenne avant l'état\\\"},\\\"schema\\\":\\\"metric\\\"},{\\\"id\\\":\\\"4\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"avg\\\",\\\"params\\\":{\\\"field\\\":\\\"taskDuration\\\",\\\"customLabel\\\":\\\"Durée moyenne état à état\\\"},\\\"schema\\\":\\\"metric\\\"}],\\\"params\\\":{\\\"perPage\\\":150,\\\"showPartialRows\\\":false,\\\"showMetricsAtAllLevels\\\":false,\\\"sort\\\":{\\\"columnIndex\\\":null,\\\"direction\\\":null},\\\"showTotal\\\":false,\\\"totalFunc\\\":\\\"sum\\\",\\\"percentageCol\\\":\\\"\\\"}}";
224         ( (JSONObject) bar.get( "attributes" ) ).put( "visState", visState );
225         SavedObjectService.create( bar );
226     }
227 
228     /**
229      * create form response by month datatable visualization
230      * 
231      * @param visualization
232      *            visualisation
233      */
234     private static void createResponseByMonthVisualization( String strTitle, String strIdVisualization, String strIdIndexPattern, int nFormId )
235     {
236         JSONObject bar = VisualizationService.getVisualizationObject( strTitle, strIdVisualization, strIdIndexPattern,
237                 "documentTypeName.keyword : formResponse and formId : " + nFormId );
238         String visState = "{\\\"title\\\":\\\"" + strTitle
239                 + "\\\",\\\"type\\\":\\\"table\\\",\\\"aggs\\\":[{\\\"id\\\":\\\"1\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"count\\\",\\\"params\\\":{\\\"customLabel\\\":\\\"Nombre de réponses\\\"},\\\"schema\\\":\\\"metric\\\"},{\\\"id\\\":\\\"2\\\",\\\"enabled\\\":true,\\\"type\\\":\\\"date_histogram\\\",\\\"params\\\":{\\\"field\\\":\\\"timestamp\\\",\\\"timeRange\\\":{\\\"from\\\":\\\"now-90d\\\",\\\"to\\\":\\\"now\\\"},\\\"useNormalizedEsInterval\\\":true,\\\"scaleMetricValues\\\":false,\\\"interval\\\":\\\"M\\\",\\\"drop_partials\\\":false,\\\"min_doc_count\\\":1,\\\"extended_bounds\\\":{},\\\"customLabel\\\":\\\"Mois\\\"},\\\"schema\\\":\\\"bucket\\\"}],\\\"params\\\":{\\\"perPage\\\":10,\\\"showPartialRows\\\":false,\\\"showMetricsAtAllLevels\\\":false,\\\"sort\\\":{\\\"columnIndex\\\":null,\\\"direction\\\":null},\\\"showTotal\\\":false,\\\"totalFunc\\\":\\\"sum\\\",\\\"percentageCol\\\":\\\"\\\",\\\"row\\\":false}}";
240         ( (JSONObject) bar.get( "attributes" ) ).put( "visState", visState );
241         SavedObjectService.create( bar );
242     }
243 }