Yi EungJun 2014-10-22
Diagnostic: Introduce the Diagnostic
Site Administrator can see the list of problems of Yobi in Site
Management > Diagnostics. I believe this way is much better than finding
an error log among the tons of log entries.

When a site administrator tries to see the list, the Diagnostic runs
every diagnostic item added by Yobi developers. The developers can add
their own diagnostic item by using Diagnostic.register method.
@cca0ece843bdf3b405dad21739aec48cb8c26df6
app/Global.java
--- app/Global.java
+++ app/Global.java
@@ -46,10 +46,21 @@
 import play.mvc.Result;
 import play.libs.F.Promise;
 
-import utils.AccessControl;
-import utils.AccessLogger;
-import utils.ErrorViews;
-import utils.YamlUtil;
+import play.mvc.Result;
+import play.mvc.Results;
+import utils.*;
+import views.html.welcome.restart;
+import views.html.welcome.secret;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.SecureRandom;
+import java.util.Date;
 
 import views.html.welcome.secret;
 import views.html.welcome.restart;
@@ -69,6 +80,7 @@
         isSecretInvalid = equalsDefaultSecret();
         insertInitialData();
 
+        Config.onStart();
         PullRequest.onStart();
         NotificationMail.onStart();
         NotificationEvent.onStart();
app/controllers/SiteApp.java
--- app/controllers/SiteApp.java
+++ app/controllers/SiteApp.java
@@ -268,4 +268,12 @@
         return ok(update.render("title.siteSetting", currentVersion,
                     YobiUpdate.versionToUpdate, exception));
     }
+
+    /**
+     * Diagnose Yobi
+     * @return
+     */
+    public static Result diagnose() {
+        return ok(diagnostic.render("title.siteSetting", Diagnostic.checkAll()));
+    }
 }
app/utils/Config.java
--- app/utils/Config.java
+++ app/utils/Config.java
@@ -34,6 +34,23 @@
 public class Config {
     public static final String DEFAULT_SCHEME = "http";
 
+    public static void onStart() {
+        Diagnostic.register(new SimpleDiagnostic() {
+            @Override
+            public String checkOne() {
+                Configuration config = Configuration.root();
+
+                if (config.getInt("application.port") != null
+                        && config.getInt("application.hostname") == null) {
+                    return "application.port may be ignored because " +
+                            "application.hostname is not configured.";
+                } else {
+                    return null;
+                }
+            }
+        });
+    }
+
     public static String getSiteName() {
         return ObjectUtils.defaultIfNull(
                 play.Configuration.root().getString("application.siteName"), "Yobi");
@@ -57,10 +74,6 @@
                return hostname;
             }
         } else {
-           if (play.Configuration.root().getInt("application.port") != null) {
-               play.Logger.warn("application.port is ignored because application.hostname is not" +
-                       " configured.");
-           }
            return defaultValue;
         }
     }
 
app/utils/Diagnostic.java (added)
+++ app/utils/Diagnostic.java
@@ -0,0 +1,64 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2014 NAVER Corp.
+ * http://yobi.io
+ *
+ * @Author Yi EungJun
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package utils;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+abstract public class Diagnostic {
+
+    private final static List<Diagnostic> diagnostics = new CopyOnWriteArrayList<>();
+
+    /**
+     * Register a diagnostic.
+     *
+     * Registered diagnostics are run when someone open Site Management >
+     * Diagnostics page.
+     *
+     * @param diagnostic
+     */
+    public static void register(@Nonnull Diagnostic diagnostic) {
+        diagnostics.add(diagnostic);
+    }
+
+    @Nonnull
+    public static List<String> checkAll() {
+        List<String> errors = new ArrayList<>();
+
+        for (Diagnostic diagnostic : diagnostics) {
+            try {
+                errors.addAll(diagnostic.check());
+            } catch (Exception e) {
+                errors.add("Failed to diagnose: "
+                        + ExceptionUtils.getStackTrace(e));
+            }
+        }
+
+        return errors;
+    }
+
+    @Nonnull
+    abstract public List<String> check();
+}
 
app/utils/SimpleDiagnostic.java (added)
+++ app/utils/SimpleDiagnostic.java
@@ -0,0 +1,43 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2014 NAVER Corp.
+ * http://yobi.io
+ *
+ * @Author Yi EungJun
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package utils;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+
+abstract public class SimpleDiagnostic extends Diagnostic {
+    @Override
+    @Nonnull
+    public List<String> check() {
+        List<String> errors = new ArrayList<>();
+
+        String error = checkOne();
+
+        if (error != null) {
+            errors.add(error);
+        }
+
+        return errors;
+    }
+
+    abstract public String checkOne();
+}
 
app/views/site/diagnostic.scala.html (added)
+++ app/views/site/diagnostic.scala.html
@@ -0,0 +1,41 @@
+@**
+* Yobi, Project Hosting SW
+*
+* Copyright 2014 NAVER Corp.
+* http://yobi.io
+*
+* @Author Yi EungJun
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+**@
+@(message: String, errors: List[String])
+
+@import org.apache.commons.lang.exception.ExceptionUtils
+@import utils.TemplateHelper._
+
+@siteMngLayout(message) {
+    <div class="title_area">
+        <h2 class="pull-left">@Messages("site.sidebar.diagnostics")</h2>
+    </div>
+
+    @if(errors.isEmpty()) {
+        <p>@Messages("site.diagnostic.errorNotFound")</p>
+    } else {
+        <p>@Messages("site.diagnostic.errorFound", errors.size())</p>
+        <ul>
+        @for(error <- errors) {
+            <li><pre>@error</pre></li>
+        }
+        </ul>
+    }
+}
app/views/site/siteMngLayout.scala.html
--- app/views/site/siteMngLayout.scala.html
+++ app/views/site/siteMngLayout.scala.html
@@ -64,6 +64,9 @@
                         @if(YobiUpdate.versionToUpdate != null) { <span class="notification-badge">1</span> }
                         </a>
                     </li>
+                    <li class="@isActiveMenu(routes.SiteApp.diagnose())">
+                        <a href="@routes.SiteApp.diagnose()">@Messages("site.sidebar.diagnostics")</a>
+                    </li>
                 </ul>
             </div>
             <div class="span10">
conf/messages
--- conf/messages
+++ conf/messages
@@ -728,6 +728,8 @@
 search.scope.group = This Group
 search.scope.porject = This Project
 site = Site
+site.diagnostic.errorFound = {0} errors were found
+site.diagnostic.errorNotFound = No errors were found
 site.features.codeManagement = Your code is safely stored in a version controlled system.
 site.features.codeReview = Review all changes in code with your team before merging. Code discussion will help you improve your code.
 site.features.issueTracker = Yobi provides an issue tracker to help you deal with your issues more easily and clearly.
@@ -763,6 +765,7 @@
 site.sidebar.projectList = Projects
 site.sidebar.userList = Users
 site.sidebar.update = Software Update
+site.sidebar.diagnostics = Diagnostics
 site.update.currentVersion = Current version is Yobi {0}
 site.update.download = Download
 site.update.isAvailable = Yobi {0} is available
conf/messages.ko
--- conf/messages.ko
+++ conf/messages.ko
@@ -729,6 +729,8 @@
 search.scope.group = 현재 그룹
 search.scope.porject = 현재 프로젝트
 site = 사이트
+site.diagnostic.errorFound = {0}개의 문제점이 발견되었습니다.
+site.diagnostic.errorNotFound = 아무런 문제가 발견되지 않았습니다.
 site.features.codeManagement = 작성한 코드는 모두 이력이 관리되는 형태로 안전하게 서버에 보관됩니다.
 site.features.codeReview = 변경된 코드를 보면서 팀원들과 토론해보세요. 코드의 완성도를 더욱 높일 수 있습니다.
 site.features.issueTracker = 팀이 함께 고민하고 처리해야 하는 내용들을 적고 거친 파도를 합심해 헤쳐나가듯 해결해 나갑니다.
@@ -764,6 +766,7 @@
 site.sidebar.projectList = 프로젝트
 site.sidebar.userList = 사용자
 site.sidebar.update = 업데이트
+site.sidebar.diagnostics = 시스템 진단
 site.update.currentVersion = 현재 버전은 {0} 입니다
 site.update.download = 다운로드
 site.update.error = 다음과 같이 에러가 발생하여 업데이트 할 버전을 확인하지 못했습니다.
conf/routes
--- conf/routes
+++ conf/routes
@@ -80,6 +80,7 @@
 POST           /sites/toggleAccountLock                                               controllers.SiteApp.toggleAccountLock(loginId: String, state: String ?= null, query: String ?= null)
 GET            /sites/update                                                          controllers.SiteApp.update()
 POST           /sites/unwatchUpdate                                                   controllers.SiteApp.unwatchUpdate()
+GET            /sites/diagnostic                                                      controllers.SiteApp.diagnose()
 GET            /lostPassword                                                          controllers.PasswordResetApp.lostPassword
 POST           /lostPassword                                                          controllers.PasswordResetApp.requestResetPasswordEmail()
 GET            /resetPassword                                                         controllers.PasswordResetApp.resetPasswordForm(s:String)
Add a comment
List