diff --git a/tools/cldr-apps/js/src/esm/cldrForum.mjs b/tools/cldr-apps/js/src/esm/cldrForum.mjs index e4215bbbc4e..9ad0b77eeef 100644 --- a/tools/cldr-apps/js/src/esm/cldrForum.mjs +++ b/tools/cldr-apps/js/src/esm/cldrForum.mjs @@ -432,6 +432,7 @@ function loadHandlerForSubmit(data) { cldrLoad.reloadV(); // main Forum page } else { cldrForumPanel.updatePosts(null); // Info Panel + cldrSurvey.expediteStatusUpdate(); // update forum icons (👁️‍🗨️, 💬) in the main table } } else { const post = $(".post").first(); diff --git a/tools/cldr-apps/js/src/esm/cldrSurvey.mjs b/tools/cldr-apps/js/src/esm/cldrSurvey.mjs index 2d348f10316..5e02238e089 100644 --- a/tools/cldr-apps/js/src/esm/cldrSurvey.mjs +++ b/tools/cldr-apps/js/src/esm/cldrSurvey.mjs @@ -80,10 +80,9 @@ const statusActionTable = { /** * How often to fetch updates. Default 15s. * Used only for delay in calling updateStatus. - * @property timerSpeed */ -let timerSpeed = 15000; // 15 seconds -let fastTimerSpeed = 3000; // 3 seconds +const timerSpeed = 15000; // 15 seconds +const fastTimerSpeed = 3000; // 3 seconds let statusTimeout = null; let overridedir = null; diff --git a/tools/cldr-apps/js/src/esm/cldrTable.mjs b/tools/cldr-apps/js/src/esm/cldrTable.mjs index b163b7b7a7f..45616958bac 100644 --- a/tools/cldr-apps/js/src/esm/cldrTable.mjs +++ b/tools/cldr-apps/js/src/esm/cldrTable.mjs @@ -531,7 +531,7 @@ function reallyUpdateRow(tr, theRow) { /* * Set up the "comparison cell", a.k.a. the "English" column. */ - if (comparisonCell && !comparisonCell.isSetup) { + if (comparisonCell) { updateRowEnglishComparisonCell(tr, theRow, comparisonCell); } @@ -750,6 +750,7 @@ function updateRowCodeCell(tr, theRow, cell) { * Called by updateRow. */ function updateRowEnglishComparisonCell(tr, theRow, cell) { + cldrDom.removeAllChildNodes(cell); let trHint = theRow.translationHint; // sometimes null if (theRow.displayName) { cell.appendChild( @@ -1119,7 +1120,9 @@ function appendTranslationHintIcon(parent, text, loc) { function appendForumStatus(parent, forumStatus, loc) { const el = document.createElement("span"); - el.textContent = "💬" + (forumStatus.hasOpenPosts ? "?" : "."); + el.textContent = forumStatus.hasOpenPosts + ? cldrText.get("forum_path_has_open_posts_icon") + : cldrText.get("forum_path_has_only_closed_posts_icon"); el.title = cldrText.get("forum_path_has_posts") + (forumStatus.hasOpenPosts diff --git a/tools/cldr-apps/js/src/esm/cldrText.mjs b/tools/cldr-apps/js/src/esm/cldrText.mjs index 7364ffd1ec3..9e8faa7f4e3 100644 --- a/tools/cldr-apps/js/src/esm/cldrText.mjs +++ b/tools/cldr-apps/js/src/esm/cldrText.mjs @@ -360,9 +360,11 @@ const strings = { forum_remember_vote: "⚠️ Please remember to vote – submitting a forum post does NOT cause any actual vote to be made.", - forum_path_has_posts: "This item has forum posts, ", + forum_path_has_posts: "This item has one or more forum posts, ", forum_path_has_open_posts: "some of which are open", forum_path_has_only_closed_posts: "all of which are closed", + forum_path_has_open_posts_icon: "👁️‍🗨️", + forum_path_has_only_closed_posts_icon: "💬", generic_nolocale: "No locale chosen.", defaultContent_msg: diff --git a/tools/cldr-apps/src/main/java/org/unicode/cldr/web/SurveyForum.java b/tools/cldr-apps/src/main/java/org/unicode/cldr/web/SurveyForum.java index 4e8453982ba..a4adef93822 100644 --- a/tools/cldr-apps/src/main/java/org/unicode/cldr/web/SurveyForum.java +++ b/tools/cldr-apps/src/main/java/org/unicode/cldr/web/SurveyForum.java @@ -17,6 +17,7 @@ import java.sql.Statement; import java.sql.Timestamp; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -47,6 +48,9 @@ public class SurveyForum { public static final String F_XPATH = "xpath"; + private static final ConcurrentHashMap localeForumStatusMap = + new ConcurrentHashMap<>(); + /** * Make an "html-safe" version of the given string * @@ -1058,6 +1062,7 @@ public void doForumAfterVote( autoPostDecline(locale, user, xpathId, value); autoPostClose(locale, user, xpathId, value); } + localeForumStatusMap.remove(locale); } /** @@ -1303,6 +1308,7 @@ private Integer doPostInternal(PostInfo postInfo) throws SurveyException { } CLDRLocale locale = postInfo.getLocale(); sm.getSTFactory().get(locale).nextStamp(); + localeForumStatusMap.remove(locale); return postId; } @@ -1633,44 +1639,64 @@ public static class PathForumStatus { public boolean hasPosts, hasOpenPosts; public PathForumStatus(CLDRLocale locale, String xpath) { + LocaleForumStatus lfs = + localeForumStatusMap.computeIfAbsent(locale, LocaleForumStatus::new); + if (lfs.pathsWithSomeOpenPosts.contains(xpath)) { + this.hasPosts = this.hasOpenPosts = true; + } else if (lfs.pathsWithOnlyClosedPosts.contains(xpath)) { + this.hasPosts = true; + this.hasOpenPosts = false; + } else { + this.hasPosts = this.hasOpenPosts = false; + } + } + } + + public static class LocaleForumStatus { + Set pathsWithSomeOpenPosts, pathsWithOnlyClosedPosts; + + public LocaleForumStatus(CLDRLocale locale) { + this.pathsWithSomeOpenPosts = ConcurrentHashMap.newKeySet(); + this.pathsWithOnlyClosedPosts = ConcurrentHashMap.newKeySet(); + + final String localeId = locale.getBaseName(); + final String tableName = DBUtils.Table.FORUM_POSTS.toString(); + final String query = + "SELECT xpath, MAX(is_open) FROM " + tableName + " WHERE loc=? GROUP BY xpath"; Connection conn = null; PreparedStatement ps = null; - final String tableName = DBUtils.Table.FORUM_POSTS.toString(); - final String localeId = locale.getBaseName(); - final int xpathId = CookieSession.sm.xpt.getByXpath(xpath); try { conn = DBUtils.getInstance().getAConnection(); if (conn == null) { return; } - // Expect most paths have NO posts, so check first for ANY posts (open or not). - // "LIMIT 1" may improve performance; no need to distinguish 1 from larger numbers - ps = - DBUtils.prepareForwardReadOnly( - conn, - "SELECT ID FROM " + tableName + " WHERE loc=? and xpath=? LIMIT 1"); + ps = DBUtils.prepareForwardReadOnly(conn, query); ps.setString(1, localeId); - ps.setInt(2, xpathId); - if (DBUtils.sqlCount(ps) <= 0) { // sqlCount returns -1 (not 0) for none! - this.hasPosts = this.hasOpenPosts = false; - return; + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + int xp = rs.getInt(1); + if (xp <= 0) { + continue; + } + String xpath = CookieSession.sm.xpt.getById(xp); + if (xpath == null) { + continue; + } + int openCount = rs.getInt(2); + if (openCount > 0) { + this.pathsWithSomeOpenPosts.add(xpath); + } else { + this.pathsWithOnlyClosedPosts.add(xpath); + } } - this.hasPosts = true; - // Check for OPEN posts - ps = - DBUtils.prepareForwardReadOnly( - conn, - "SELECT ID FROM " - + tableName - + " WHERE loc=? and xpath=? AND is_open=1 LIMIT 1"); - ps.setString(1, localeId); - ps.setInt(2, xpathId); - this.hasOpenPosts = DBUtils.sqlCount(ps) > 0; + rs.close(); } catch (SQLException e) { - SurveyLog.logException( - logger, - e, - "PathForumStatus for " + tableName + " " + locale + ":" + xpathId); + String complaint = + "SurveyForum: Error getting status for locale " + + localeId + + " - " + + DBUtils.unchainSqlException(e); + logger.severe(complaint); } finally { DBUtils.close(ps, conn); }