This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git


The following commit(s) were added to refs/heads/master by this push:
     new 60dec6c  SLING-11102 - Adding a console for getting the query plan for 
queries and seeing the top / slowest queries
60dec6c is described below

commit 60dec6c451152b12b0bb0579ea6b5731163ad795
Author: Dan Klco <[email protected]>
AuthorDate: Sun Jan 30 17:11:49 2022 -0500

    SLING-11102 - Adding a console for getting the query plan for queries and 
seeing the top / slowest queries
---
 core/pom.xml                                       |   9 ++
 .../sling/cms/core/models/QueryDebugger.java       | 147 +++++++++++++++++++++
 .../sling/cms/core/models/QueryDebuggerTest.java   |  64 +++++++++
 i18n-helper/i18n/de.json                           |  12 +-
 i18n-helper/i18n/zh.json                           |  11 +-
 i18n-helper/src/messages.json                      |  10 +-
 pom.xml                                            |   7 +-
 ui/bnd.bnd                                         |   2 +-
 ui/src/main/frontend/scss/cms.scss                 |   4 +
 .../components/cms/querydebug/querydebug.jsp       | 103 +++++++++++++++
 .../libs/sling-cms/content/admin/querydebug.json   |  66 +++++++++
 .../jcr_root/libs/sling-cms/content/start.json     |   6 +
 .../resources/jcr_root/libs/sling-cms/i18n.json    |   2 +-
 13 files changed, 435 insertions(+), 8 deletions(-)

diff --git a/core/pom.xml b/core/pom.xml
index 06f7f3f..e4108a2 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -187,6 +187,10 @@
             <artifactId>org.apache.sling.testing.sling-mock.junit4</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.sling-mock-oak</artifactId>
+        </dependency>
+        <dependency>
             <groupId>javax.annotation</groupId>
             <artifactId>javax.annotation-api</artifactId>
         </dependency>
@@ -206,5 +210,10 @@
             <groupId>org.jetbrains</groupId>
             <artifactId>annotations</artifactId>
         </dependency>
+        <dependency>
+            <groupId>javax.management.j2ee</groupId>
+            <artifactId>javax.management.j2ee-api</artifactId>
+        </dependency>
+
     </dependencies>
 </project>
\ No newline at end of file
diff --git 
a/core/src/main/java/org/apache/sling/cms/core/models/QueryDebugger.java 
b/core/src/main/java/org/apache/sling/cms/core/models/QueryDebugger.java
new file mode 100644
index 0000000..7ce7012
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/models/QueryDebugger.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.sling.cms.core.models;
+
+import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.Row;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.TabularData;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.Self;
+import org.osgi.annotation.versioning.ProviderType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ProviderType
+@Model(adaptables = SlingHttpServletRequest.class)
+public class QueryDebugger {
+
+    private static final String MBEAN_NAME = 
"org.apache.jackrabbit.oak:name=Oak Query Statistics,type=QueryStat";
+
+    private static final Logger log = 
LoggerFactory.getLogger(QueryDebugger.class);
+    private final String plan;
+    private final String exception;
+    private final String statement;
+    private final List<Map<String, Object>> slowQueries = new ArrayList<>();
+    private final List<Map<String, Object>> popularQueries = new ArrayList<>();
+
+    @Inject
+    public QueryDebugger(@Self SlingHttpServletRequest request) {
+
+        Optional<String> statementParam = 
Optional.ofNullable(request.getParameter("statement"));
+        String language = 
Optional.ofNullable(request.getParameter("language")).orElse(Query.JCR_SQL2);
+
+        String lplan = null;
+        String lexception = null;
+        String lstatement = null;
+        try {
+            if (statementParam.isPresent()) {
+
+                QueryManager queryManager = 
request.getResourceResolver().adaptTo(Session.class).getWorkspace()
+                        .getQueryManager();
+                Query query = queryManager.createQuery("explain " + 
statementParam.get(), language);
+                Row row = query.execute().getRows().nextRow();
+                lplan = row.getValue("plan").getString();
+                lstatement = statementParam.get();
+            }
+        } catch (RepositoryException re) {
+            lexception = re.toString();
+            log.warn("Failed to debug query: {}", statementParam, re);
+        } finally {
+            this.plan = lplan;
+            this.exception = lexception;
+            this.statement = lstatement;
+        }
+
+        try {
+            collectMbeanData("PopularQueries", popularQueries);
+            collectMbeanData("SlowQueries", slowQueries);
+        } catch (MBeanException | MalformedObjectNameException | 
InstanceNotFoundException | AttributeNotFoundException
+                | NullPointerException | ReflectionException e) {
+            log.warn("Failed to load mBean data", e);
+        }
+    }
+
+    private void collectMbeanData(String attributeName, List<Map<String, 
Object>> target)
+            throws MalformedObjectNameException, NullPointerException, 
InstanceNotFoundException,
+            AttributeNotFoundException, ReflectionException, MBeanException {
+        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+        ObjectName mbeanName = ObjectName.getInstance(MBEAN_NAME);
+        TabularData data = (TabularData) mBeanServer.getAttribute(mbeanName, 
attributeName);
+        data.values().stream().map(CompositeData.class::cast)
+                .forEach(compositeData -> 
target.add(compositeData.getCompositeType().keySet().stream()
+                        .collect(Collectors.toMap(k -> k, 
compositeData::get))));
+    }
+
+    /**
+     * @return the plan
+     */
+    public String getPlan() {
+        return plan;
+    }
+
+    /**
+     * @return the exception
+     */
+    public String getException() {
+        return exception;
+    }
+
+    /**
+     * @return the statement
+     */
+    public String getStatement() {
+        return statement;
+    }
+
+    /**
+     * @return the slowQueries
+     */
+    public List<Map<String, Object>> getSlowQueries() {
+        return slowQueries;
+    }
+
+    /**
+     * @return the popularQueries
+     */
+    public List<Map<String, Object>> getPopularQueries() {
+        return popularQueries;
+    }
+
+}
diff --git 
a/core/src/test/java/org/apache/sling/cms/core/models/QueryDebuggerTest.java 
b/core/src/test/java/org/apache/sling/cms/core/models/QueryDebuggerTest.java
new file mode 100644
index 0000000..85a1159
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/models/QueryDebuggerTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sling.cms.core.models;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import javax.jcr.query.Query;
+
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class QueryDebuggerTest {
+
+    @Rule
+    public SlingContext context = new 
SlingContext(ResourceResolverType.JCR_OAK);
+
+    @Test
+    public void testNoParams() {
+        QueryDebugger debugger = new QueryDebugger(context.request());
+        assertNull(debugger.getException());
+        assertNull(debugger.getPlan());
+        assertNull(debugger.getStatement());
+    }
+
+    @Test
+    public void testExplain() {
+        context.request().addRequestParameter("statement", "SELECT * FROM 
[nt:base]");
+        context.request().addRequestParameter("language", Query.JCR_SQL2);
+
+        QueryDebugger debugger = new QueryDebugger(context.request());
+        assertNotNull(debugger.getPlan());
+        assertNull(debugger.getException());
+        assertNotNull(debugger.getStatement());
+    }
+
+    @Test
+    public void testFailure() {
+        context.request().addRequestParameter("statement", "SELECT * FROM 
[nt:base]");
+        context.request().addRequestParameter("language", Query.XPATH);
+
+        QueryDebugger debugger = new QueryDebugger(context.request());
+        assertNull(debugger.getPlan());
+        assertNotNull(debugger.getException());
+        assertNull(debugger.getStatement());
+    }
+
+}
diff --git a/i18n-helper/i18n/de.json b/i18n-helper/i18n/de.json
index 7b14d02..602cec3 100644
--- a/i18n-helper/i18n/de.json
+++ b/i18n-helper/i18n/de.json
@@ -254,7 +254,7 @@
   "JSON": "JSON",
   "Job": "Job",
   "Jobs": "Jobs",
-  "Job Name": "Name des Jobs",
+  "Job Name": "Name des Jobs",
   "Jpeg": "Jpeg",
   "Key": "Schlüssel",
   "Keywords": "Schlüsselwort",
@@ -590,5 +590,13 @@
   "i18n Dictionaries": "I18N-Verzeichnisse",
   "i18n Dictionarties": "I18N-Verzeichnisse",
   "i18n Dictionary": "I18N-Verzeichnis",
-  "i18n Dictionary Entries": "I18N-Verzeichniseinträge"
+  "i18n Dictionary Entries": "I18N-Verzeichniseinträge",
+  "Debug Query": "",
+  "Duration": "",
+  "Plan": "",
+  "Popular Queries": "",
+  "Query Debugger": "",
+  "Query Language": "",
+  "Query Statement": "",
+  "Slow Queries": ""
 }
\ No newline at end of file
diff --git a/i18n-helper/i18n/zh.json b/i18n-helper/i18n/zh.json
index f3554e4..8c594fd 100644
--- a/i18n-helper/i18n/zh.json
+++ b/i18n-helper/i18n/zh.json
@@ -593,12 +593,19 @@
   "Existing Path": "存在路径",
   "Replacing properties under path": "替换此路径内所有资源的属性",
   "Select Position": "选择位置",
-  "Job Name": "",
   "None": "",
   "Create": "",
   "Delete Rendition": "",
   "Download Rendition": "",
   "Rendition": "",
   "Rendition Name": "",
-  "Renditions": ""
+  "Renditions": "",
+  "Debug Query": "",
+  "Duration": "",
+  "Plan": "",
+  "Popular Queries": "",
+  "Query Debugger": "",
+  "Query Language": "",
+  "Query Statement": "",
+  "Slow Queries": ""
 }
\ No newline at end of file
diff --git a/i18n-helper/src/messages.json b/i18n-helper/src/messages.json
index 7f08fcd..ac46de5 100644
--- a/i18n-helper/src/messages.json
+++ b/i18n-helper/src/messages.json
@@ -116,6 +116,7 @@
   "Date",
   "Date/Time",
   "Datetime Local",
+  "Debug Query",
   "Default",
   "Default File Editor",
   "Default Layout",
@@ -153,6 +154,7 @@
   "Download Rendition",
   "Download file",
   "Drop invalid queue items",
+  "Duration",
   "Edit",
   "Edit Configuration",
   "Edit Configuration Properties",
@@ -254,7 +256,7 @@
   "JSON",
   "Job",
   "Jobs",
-  "Job Name",
+  "Job Name",
   "Jpeg",
   "Key",
   "Keywords",
@@ -359,8 +361,10 @@
   "Paths",
   "Pattern",
   "Placeholder",
+  "Plan",
   "Policy",
   "Policy Path",
+  "Popular Queries",
   "Position",
   "Predecessors",
   "Preview",
@@ -388,6 +392,9 @@
   "Published Date",
   "Pull Items",
   "Query",
+  "Query Debugger",
+  "Query Language",
+  "Query Statement",
   "Queue",
   "Queue Processing Enabled",
   "Queue Provider",
@@ -489,6 +496,7 @@
   "Sling CMS - Transformation Size Handler",
   "Sling CMS - Transformations Configuration",
   "Sling CMS Thumbnails",
+  "Slow Queries",
   "Start",
   "Start Job",
   "Started",
diff --git a/pom.xml b/pom.xml
index 13ec543..df1cc49 100644
--- a/pom.xml
+++ b/pom.xml
@@ -331,6 +331,12 @@
         <version>${osgi-annotation-version}</version>
         <scope>provided</scope>
       </dependency>
+      <dependency>
+        <groupId>javax.management.j2ee</groupId>
+        <artifactId>javax.management.j2ee-api</artifactId>
+        <version>1.1.2</version>
+        <scope>provided</scope>
+      </dependency>
       <!-- Document Processing Dependencies -->
       <dependency>
         <artifactId>jsoup</artifactId>
@@ -438,4 +444,3 @@
     </pluginManagement>
   </build>
 </project>
-
diff --git a/ui/bnd.bnd b/ui/bnd.bnd
index d562761..ceeb2c7 100644
--- a/ui/bnd.bnd
+++ b/ui/bnd.bnd
@@ -1,3 +1,3 @@
 Sling-Nodetypes: SLING-INF/nodetypes/nodetypes.cnd
-Sling-Initial-Content: 
jcr_root;overwriteProperties=true;overwrite=false;ignoreImportProviders:=xml,jcr_root/conf/global;overwrite=true;ignoreImportProviders:=xml;path:=/conf/global,jcr_root/etc/clientlibs;overwrite=true;ignoreImportProviders:=xml;path:=/etc/clientlibs,jcr_root/etc/taxonomy;overwrite:=false;uninstall:=true;path:=/etc/taxonomy,jcr_root/oak%3Aindex;overwrite:=false;uninstall:=true;path:=/oak:index,jcr_root/libs/sling-cms;overwrite:=true;uninstall:=true;path:=/libs/sling-cm
 [...]
+Sling-Initial-Content: 
jcr_root;overwriteProperties:=true;overwrite:=false;ignoreImportProviders:=xml,jcr_root/conf/global;overwrite:=true;ignoreImportProviders:=xml;path:=/conf/global,jcr_root/etc/taxonomy;overwrite:=false;uninstall:=true;path:=/etc/taxonomy,jcr_root/oak%3Aindex;overwrite:=false;uninstall:=true;path:=/oak:index,jcr_root/libs/sling-cms;overwrite:=true;uninstall:=true;path:=/libs/sling-cms,jcr_root/libs/sling/thumbnails;overwrite:=true;uninstall:=true;path:=/libs/sling/th
 [...]
 -includeresource: target/frontend/dist
\ No newline at end of file
diff --git a/ui/src/main/frontend/scss/cms.scss 
b/ui/src/main/frontend/scss/cms.scss
index c9d1463..a77dde7 100644
--- a/ui/src/main/frontend/scss/cms.scss
+++ b/ui/src/main/frontend/scss/cms.scss
@@ -261,6 +261,10 @@ nav .level-right {
   padding: 1em;
 }
 
+#query-debug {
+  height: calc(100vh - 500px);
+}
+
 .rte-form {
   margin: 0.5em 0;
 }
diff --git 
a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/querydebug/querydebug.jsp
 
b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/querydebug/querydebug.jsp
new file mode 100644
index 0000000..baa79c8
--- /dev/null
+++ 
b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/querydebug/querydebug.jsp
@@ -0,0 +1,103 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */ --%>
+<%@include file="/libs/sling-cms/global.jsp"%>
+<div id="query-debug" class="scroll-container reload-container">
+    <sling:adaptTo adaptable="${slingRequest}" 
adaptTo="org.apache.sling.cms.core.models.QueryDebugger" var="queryDebugger" />
+    <br/><hr/><br/>
+    <dl>
+    <c:if test="${not empty queryDebugger.statement}">
+        <dt><fmt:message key="Query Statement" /><dt>
+        <dd>${sling:encode(queryDebugger.statement,'HTML')}</dd>
+    </c:if>
+    <c:if test="${not empty queryDebugger.plan}">
+        <dt><fmt:message key="Plan" /><dt>
+        <dd>${sling:encode(queryDebugger.plan,'HTML')}</dd>
+    </c:if>
+    <c:if test="${not empty queryDebugger.exception}">
+        <dt><fmt:message key="Exception" /><dt>
+        <dd>${sling:encode(queryDebugger.exception,'HTML')}</dd>
+    </c:if>
+    <br/><hr/><br/>
+    <h2><fmt:message key="Popular Queries" /></h2>
+    <table class="table">
+        <tr>
+            <th>
+                <fmt:message key="Query Statement" />
+            </th>
+            <th>
+                <fmt:message key="Query Language" />
+            </th>
+            <th>
+                <fmt:message key="Count" />
+            </th>
+            <th>
+                <fmt:message key="Duration" />
+            </th>
+        </tr>
+        <c:forEach var="query" items="${queryDebugger.popularQueries}">
+            <tr>
+                <td>
+                    ${query.statement}
+                </td>
+                <td>
+                    ${query.language}
+                </td>
+                <td>
+                    ${query.occurrenceCount}
+                </td>
+                <td>
+                    ${query.duration}
+                </td>
+            </tr>
+        </c:forEach>
+    </table>
+    <h2><fmt:message key="Slow Queries" /></h2>
+    <table class="table">
+        <tr>
+            <th>
+                <fmt:message key="Query Statement" />
+            </th>
+            <th>
+                <fmt:message key="Query Language" />
+            </th>
+            <th>
+                <fmt:message key="Count" />
+            </th>
+            <th>
+                <fmt:message key="Duration" />
+            </th>
+        </tr>
+        <c:forEach var="query" items="${queryDebugger.slowQueries}">
+            <tr>
+                <td>
+                    ${query.statement}
+                </td>
+                <td>
+                    ${query.language}
+                </td>
+                <td>
+                    ${query.occurrenceCount}
+                </td>
+                <td>
+                    ${query.duration}
+                </td>
+            </tr>
+        </c:forEach>
+    </table>
+</div>
\ No newline at end of file
diff --git 
a/ui/src/main/resources/jcr_root/libs/sling-cms/content/admin/querydebug.json 
b/ui/src/main/resources/jcr_root/libs/sling-cms/content/admin/querydebug.json
new file mode 100644
index 0000000..7e6ebbc
--- /dev/null
+++ 
b/ui/src/main/resources/jcr_root/libs/sling-cms/content/admin/querydebug.json
@@ -0,0 +1,66 @@
+{
+  "jcr:primaryType": "sling:Page",
+  "jcr:content": {
+    "sling:resourceType": "sling-cms/components/pages/base",
+    "jcr:title": "Query Debugger",
+    "jcr:primaryType": "nt:unstructured",
+    "container": {
+      "jcr:primaryType": "nt:unstructured",
+      "sling:resourceType": "sling-cms/components/general/container",
+      "title": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/textelement",
+        "i18n": true,
+        "level": "h3",
+        "text": "Query Debugger"
+      },
+      "getform": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/cms/getform",
+        "button": "Debug Query",
+        "load": "#query-debug",
+        "target": "#query-debug",
+        "action": 
"/libs/sling-cms/content/admin/querydebug/jcr:content/container/querydebug.html",
+        "skipcancel": true,
+        "fields": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "sling-cms/components/general/container",
+          "statement": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": 
"sling-cms/components/editor/fields/textarea",
+            "label": "Query Statement",
+            "name": "statement"
+          },
+          "language": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Query Language",
+            "name": "language",
+            "options": {
+              "JCR-SQL2": {
+                "label": "JCR-SQL2",
+                "value": "JCR-SQL2"
+              },
+              "xpath": {
+                "label": "xpath",
+                "value": "xpath"
+              },
+              "JCR-JQOM": {
+                "label": "JCR-JQOM",
+                "value": "JCR-JQOM"
+              },
+              "sql": {
+                "label": "SQL",
+                "value": "sql"
+              }
+            }
+          }
+        }
+      },
+      "querydebug": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/cms/querydebug"
+      }
+    }
+  }
+}
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json 
b/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
index da14f8f..32bce47 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
@@ -135,6 +135,12 @@
             "link": "/cms/publication/home.html",
             "text": "Publication"
           },
+          "querydebug": {
+            "jcr:primaryType": "nt:unstructured",
+            "enabledGroups": ["administrators"],
+            "link": "/cms/admin/querydebug.html",
+            "text": "Query Debugger"
+          },
           "systemconsole": {
             "jcr:primaryType": "nt:unstructured",
             "enabledGroups": ["administrators"],
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/i18n.json 
b/ui/src/main/resources/jcr_root/libs/sling-cms/i18n.json
index 664c38e..83c871b 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/i18n.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/i18n.json
@@ -4211,7 +4211,7 @@
     "msg-job-name": {
       "jcr:primaryType": "sling:MessageEntry",
       "sling:message": "Name des Jobs",
-      "sling:key": "Job Name"
+      "sling:key": "Job Name"
     },
     "msg-jpeg": {
       "jcr:primaryType": "sling:MessageEntry",

Reply via email to